Developers used to bounce between their editor, a ticketing system and a separate security dashboard. In 2026 they bounce a lot less — because much of their day now happens inside Claude Code, Cursor, Windsurf and Gemini Code Assist. If a security tool doesn't meet them there, it simply doesn't get adopted.
This post describes how we built the SF365 Claude Code Skill: a zero-install, Python-stdlib-only skill that turns "scan this repo" into a real enterprise-grade scan of an application. Every finding still lands in the full Security Factor 365 portal — dashboards, compliance reports, Slack and Jira notifications — but the developer never has to leave the prompt.
Why a skill, not just an MCP server? SF365 already ships a 28-tool MCP server that AI agents can query. A Claude Code skill is different: it is a self-contained capability the developer explicitly installs and invokes with a slash command. Skills shine for human-in-the-loop workflows where the developer, not the agent, is the conductor.
The design brief
We set four constraints before writing a single line of code:
- Zero install friction. No
pip install, no Docker, no native binaries. If a developer has Python 3.8+ they can use it immediately. - No server-side changes. The skill must ride on the existing REST API, not force new endpoints just to support it.
- Credentials never in the prompt. A JWT is exchanged once and cached with
0600permissions on Unix; the chat transcript never sees the password or token. - Portal remains system of record. Findings persist in the database; dashboards, PDF reports and compliance mappings are fully available afterwards.
Architecture in ten seconds
The skill is four small Python files behind one SKILL.md manifest:
SF.Skill/
+-- README.md
+-- LICENSE
+-- .gitignore
+-- skills/sf365/
+-- SKILL.md # Claude Code manifest
+-- scripts/
+-- sf365_client.py # HTTP + token cache
+-- sf365_auth.py # login / status / logout
+-- sf365_apps.py # list applications
+-- sf365_scan.py # trigger scan
+-- sf365_findings.py # list findings
The SKILL.md describes the skill to Claude — purpose, env vars, when to call which script. The Python scripts speak the SF365 REST API directly. Everything uses urllib.request and json from the standard library.
Authentication without theatre
SF365's mobile REST API already issues signed JWTs via POST /api/mobile/auth/login — we reuse exactly that endpoint. The first time the developer invokes the skill, it reads SF365_EMAIL and SF365_PASSWORD from the environment (or prompts for them), exchanges them for a token, and writes:
~/.sf365/credentials.json # chmod 0600 on Unix
{
"token": "eyJhbGciOi...",
"refreshToken": "base64-entropy...",
"user": { "id": 1, "email": "you@company.com", ... },
"baseUrl": "https://sf.peopleworksservices.com"
}
Subsequent commands read the token from that file and attach it as Authorization: Bearer <token>. If the token expires, 401 triggers a one-line prompt to re-authenticate. The developer's plaintext password is never logged, never sent back to Claude, and never written to disk.
The scanning round-trip
A typical session looks like this:
- Developer types "scan the current repo with SF365, show me anything Critical".
- Claude reads
SKILL.md, checks authentication viasf365_auth.py status, and sees we are authenticated. - Claude calls
sf365_apps.pyto list applications and picks the one whoseRepositoryUrlmatches the repo the developer has open. - Claude calls
sf365_scan.py <appId> Full— this hits the existing anonymous/api/webhooks/genericendpoint, which kicks off all 11 scanner engines and returns the counts. - Claude calls
sf365_findings.py <appId> Critical— this GETs/api/mobile/applications/{id}/findings, filters client-side, and returns JSON. - Claude formats the top findings as a markdown table inline in the chat and drops a link to
https://sf.peopleworksservices.com/applications/<id>for the full experience.
The total wall-clock time is dominated by the scan itself. The skill adds negligible overhead.
The parts we deliberately did not build
It is worth listing what the skill is not, because those omissions are features:
- No bundled scanner. All detection logic lives in the server. Skill upgrades do not require re-running scanners client-side — just
git pull. - No SAAS of its own. The skill has no state beyond the token cache. Delete the folder and it is gone.
- No custom dashboards. The portal owns dashboards; the skill just deep-links.
- No Claude-specific lock-in. Because scripts are plain Python, adapting them to Cursor, Windsurf or a bash wrapper takes minutes.
What we learned shipping it
Three non-obvious lessons from the first week:
- SKILL.md is a prompt, treat it like one. Spelling out when to call which script, what outputs look like, and how to format results for the user changed reliability dramatically. Terse SKILL.md files confuse the model into guessing.
- Stdlib wins. Every dependency is a support ticket. The skill has zero pip installs and that alone is worth shipping.
- Don't gold-plate auth. Mobile JWT was perfect. Rolling a separate API-key system for the skill would have doubled the scope with no user benefit.
Where this is heading
The next iteration will:
- Auto-detect the SF365 Application by matching the current repo's
git remote. - Poll scan progress so long scans don't block the chat.
- Issue per-user long-lived API keys from the portal Settings page.
- Ship as a one-line
claude plugin add sf365install once the Claude Code plugin store supports it.
None of that is blocking today's workflow. The skill is already the fastest way a developer can go from "I want to scan this" to "here are the three Criticals I need to fix" — without ever leaving the IDE. That shift, more than any dashboard or PDF, is what changes adoption of AppSec.
Put SF365 inside your IDE today
The skill is MIT-licensed and lives in the SF365 repo under SF.Skill/skills/sf365. Install, point it at your portal, run /sf365 scan — you're in.