configure-a7726c — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited configure-a7726c (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are walking the user through configuring the sponsio-claude-code plugin so it actually wraps tool calls in this Claude Code session. Without these steps, the plugin loads but every per-plugin library is empty and every tool call passes through unguarded.
The flow has two halves: setup (steps 1–2, 4–6) covers the common case where the user runs popular MCP servers we ship starters for; scan (step 3) covers plugins / MCP servers without a bundled starter and lives inline because most sessions touch both halves.
Run:
sponsio --versionpip install sponsio (orpip install -e ".[all]" from a local clone). Stop here until they confirm.
If the user owns the agent code and wants to wire Sponsio into their own framework integration (LangGraph, OpenAI Agents, Claude Agent SDK, …) instead of running it as a Claude Code host plugin, delegate to the sponsio skill (sponsio onboard .). This skill is only for the host-plugin case where the user is gating tool calls inside their Claude Code session.
Sponsio has two layers, and rules must land in the right YAML or they do nothing. When the user hands you a policy document / instruction file / "list of things the agent must not do" and asks you to encode it, classify each rule before writing anywhere.
| Signal | → Layer 1 (this skill — write to ~/.sponsio/plugins/<id>/sponsio.yaml) | → Layer 2 (delegate to sponsio skill — writes <project>/sponsio.yaml) |
|---|---|---|
| Tool names mentioned | Bash, Edit, Write, Read, mcp__* | tool names from the user's project's tool inventory |
| Path form | absolute or ~/... paths outside the user's project | paths relative to the project (src/...) |
| Subject of the rule | "Cursor must not…", "Claude Code must not…" | "the loan agent must…", "the chatbot should…" |
| Domain language | shell, git, file system, MCP server primitives | AML, KYC, refund, PII, approval, faithfulness, hallucination |
./sponsio.yaml exists in cwd | weaker signal — Layer 2 is in play, but rule may still be Layer 1 | stronger signal — most rules belong here |
Process:
~/.sponsio/plugins/<id>/sponsio.yaml).
"rules X, Y look like rules for the agent you're building, not the IDE agent — switching to the sponsio skill (sponsio onboard) for those". Do not silently dump them into a host-plugin YAML.
which layer they mean before writing.
The default failure mode is over-writing to Layer 1 because that's this skill's home turf. Cross-layer leakage is a worse user error than the extra clarification round.
Run:
sponsio plugin initThis creates ~/.sponsio/plugins/_host/sponsio.yaml (covers Claude Code's first-party tools — Bash, Edit, Write, …) and runs a built-in allow + block smoke test. Show the user the output verbatim; if the smoke test fails, stop and surface the error — the install is broken and we shouldn't pretend otherwise.
If the file already exists (the user re-ran setup), the command prints …already exists. Re-run with --force to overwrite. That's informational, not an error.
You need to know which MCP servers / plugins the user actually has installed before deciding which libraries to ship. Run claude mcp list, or have the user open ~/.claude/settings.json and read mcpServers. Don't guess. If the user doesn't know, that's the answer — they have none beyond the built-ins, and you can skip straight to step 4.
For each registered server, decide which branch of step 3 applies:
sponsio plugin install --list
# _host (auto-installed by `plugin init`)
# filesystem
# github
# playwrightgithub, filesystem, playwright) → step 3a.A single session often hits both branches.
sponsio plugin install)Show the user what each bundled starter covers so they can pick:
_host — already installed by step 1.github — github-mcp-server. Hard-deny on delete_repository,blocks deletes of main / master / production branches, blocks writes to .env, .github/workflows/*.yml, CODEOWNERS. Caps issue / PR / merge rates.
filesystem — @modelcontextprotocol/server-filesystem. Blocksread/write/edit/move on .env, .ssh/, .aws/credentials, /etc/, browser cookie databases, etc.
playwright — microsoft/playwright-mcp. Blocks navigation tointernal hosts, browser_evaluate exfil patterns (document.cookie, sendBeacon, fetch to remote hosts), credit-card-shape typing.
Then install only what the user has — don't push the whole bundle:
sponsio plugin install github filesystem
# or, if they confirm they want everything:
sponsio plugin install --allplugin install prints a per-library digest after each successful write — categories, rule descriptions, and the YAML path. Surface the full digest to the user verbatim; do not paraphrase or summarise. The digest is the user's only chance to see what just got loaded before flipping to enforce mode. If they ask to inspect later, run:
sponsio plugin show <name> # installed library
sponsio plugin show <name> --root … # custom rootsponsio plugin scan)For every MCP server / plugin that isn't in the bundled set, generate a starter library. Don't try to hand-author one — the scanner has two engines (heuristic + LLM) that together cover most patterns better than ad-hoc rules.
Three discovery paths in priority order:
sponsio spawns the server, does the JSON-RPC handshake, calls tools/list, and auto-populates the tool inventory along with parameter schemas the LLM can use. Read the plugin's .mcp.json (or claude mcp list) to find the spawn command.
sponsio plugin scan \
--plugin-id <name> \
--target-host claude-code \
--introspect "python3 /path/to/server.py"For Claude Code MCP integrations, ALWAYS pass --target-host claude-code so tool names get the mcp__<plugin-id>__ prefix Claude Code surfaces them under.
skills/*/SKILL.md).Tool names are <plugin-id>:<skill-name>. Read <dir>/skills/ to enumerate, then pass via --tools:
sponsio plugin scan <plugin-dir> --tools my-plugin:foo,my-plugin:barinconvenient). Ask the user "what tools does this MCP server expose?" and pass them via --tools t1,t2,t3. Last resort.
ALWAYS start with the dry-run. Never --apply until the user has seen the output.
sponsio plugin scan --plugin-id <id> --target-host claude-code \
--introspect "<spawn-cmd>"The output has two parts:
rules: destructive verbs → irreversible_once, generic rate-limit / loop-detection caps. This is the deterministic floor; ship it as-is or harden further.
# === tool inventory ... === —every tool's name, description, input_schema, plus a tool_name_in_contracts field already namespaced for the target host. This is the input for the agent's own contract extraction pass (Step 3b.3).
The heuristic engine catches destructive verbs by name; the agent fills semantic gaps by reading each tool's description and input_schema. No API call needed — you (the host agent) ARE the LLM the prompt is written for.
sponsio plugin prompt claude-codePipe to a variable or just remember it for the next step.
previous step. Output should be a JSON object matching:
{"contracts": [{"desc": "...", "pattern": "...", "args": [...]}]}merge it into the heuristic library (or keep it as a separate sponsio.semantic.yaml next to the heuristic one). Each semantic contract should carry source: agent-extracted so later tooling can distinguish them from heuristic rules.
The whole loop is fast because (a) introspect + heuristic generation is one CLI call, (b) the prompt is short and on-disk (no network), (c) you reason over the inventory in your own context.
For each contract in each group, state:
English. Example: "blocks delete_repository outright; for one-off deletes the user has to add an override."
heuristic: evidence: starter_irreversible/ starter_bash / starter_sql / starter_rate_limit / starter_loop. Each comes from the rules in sponsio/discovery/starter_pack.py:_per_tool_rules.
baselines, not airtight. Tell the user explicitly when a rule feels generic so they don't assume it's covering everything.
If a rule is wrong (e.g. the heuristic flagged list_users as external-send because the name contains send), tell the user to either:
--apply.
customized:
- match: { desc: "list_users at most 10 times per session" }
disabled: trueOnce the user is happy with the dry-run:
sponsio plugin scan <plugin-dir> --tools ... --applyShow every path the apply writes to:
✓ wrote ~/.sponsio/plugins/_host/sponsio.yaml
✓ wrote ~/.sponsio/plugins/github/sponsio.yamlIf a target file already exists, scan refuses without --force. Don't pass `--force` for the user automatically — surface the warning and let them decide.
The shipped libraries are templates with conservative defaults. Without this step, half the rules are either too strict (blocking legitimate work) or too loose (giving the user false confidence). Walk through the four parameter classes below and write any agreed-upon adjustments as customized: blocks into the relevant library. Don't skip this step on the assumption defaults are fine — defaults are a starting point, not a fit.
Several rules in _host and filesystem use <workspace>/ as the path-allowlist root. Until that's substituted with the user's actual project root, those rules either match nothing (silent no-op) or match too broadly. Ask:
"Where's your main working directory for this session? (pwd or your project root.)"Then write to the relevant agents:<id> block:
agents:
_host:
workspace: /Users/<them>/projects/<repo>If multiple plugins want different workspaces, add workspace: under each agent block separately.
The shipped rate_limit defaults are tuned for a single interactive session (50 Bash calls / 200k tokens / 5 PRs). Operators running CI scripts, batch jobs, or recurring agents often hit these legitimately. Ask:
"How chatty is this agent? Is this an interactive session, a CI run, a long-running operator, or a one-shot script?"
Use the answer to override:
| Scenario | Adjustment |
|---|---|
| Interactive (default) | leave as-is |
| Heavy CI / batch | bump exec/Bash rate to 200, token budget to 500k |
| Read-only research | tighten Bash rate to 10, drop exec rate cap entirely |
| Long-running (>1h) | drop session-bounded counts, switch to time-window pacing (note: needs daemon mode for time-window — surface as a future-work caveat) |
Example customization:
agents:
_host:
customized:
- match: { desc: "Cap exec calls per session" }
args: [Bash, 200]Different blast radius means different default tightness. Ask:
"What kind of environment is this — local dev, staging, production, or a customer-data context?"
Apply this matrix:
| Profile | What changes |
|---|---|
| Local dev | leave defaults |
| Staging | enable audit_after on destructive tools (logs every action); keep delete rules permissive |
| Production | move delete_* from rate_limit 0 to assumption-gated — require an explicit confirm_reconfirmed tool emission (see existing pattern in capability/shell §4) |
| Regulated / PII | tighten the deterministic PII / dangerous-arg rules and lower their thresholds; add no_pii response checks on agents that don't currently include them |
Walk the user through each shipped rule that's commonly tripped by legitimate workflows. For every "Yes, that's a problem for me" answer, the user adds a targeted customized: entry. Common cases:
| Rule | When it false-positives | Customization |
|---|---|---|
_host "Each exec call needs its own confirm_reconfirmed" | Any agent that doesn't emit confirm_reconfirmed markers | disabled: true (until the integration ships markers) |
github "delete_repository is blocked outright" | Cleanup bots, automated repo lifecycle | disabled: true + add a new contract with a tighter pattern (only allow deletion of repos matching ^test-) |
filesystem "read_file must not exfiltrate dotenv" | dotenv rotators, secret-rotation agents | disabled: true only for read_file (keep write_file denied) |
playwright "browser_navigate must not target internal hosts" | Anyone testing their own internal app | replace with a narrower allowlist of the user's actual internal hostnames |
Per-plugin libraries live in a single file:
~/.sponsio/plugins/<id>/sponsio.yamlInside, shipped contracts carry source: bundle:<id> (stamped at install). The user's customisations sit beside them in the same file:
contracts: list(without a source: bundle:* tag — anything user-authored).
customized:block: match: { desc: "..." } plus disabled: true / re-tuned args: / narrowed A:.
sponsio plugin install <id> (or sponsio host install <host>) is idempotent — re-run it any time to pull a new bundle (e.g. after pip install -U sponsio) without losing customisations. Default contracts are wholesale replaced from the new bundle; everything user-authored — every contract without the bundle source tag, plus the entire customized: block — is preserved verbatim. Hand-editing a default contract's body in place is the one thing that doesn't survive (same model as brew upgrade clobbering a hand-edited formula); always express changes as a customized: entry instead.
The agent must NOT use Edit, Write, MultiEdit, or shell redirects on this file — the runtime self-modify pack blocks those calls. The agent also must NOT hand the user a YAML snippet it composed from the conversation. Legitimate sources of contract content:
sponsio plugin install shipspolicy docs, or a tool inventory
An agent-authored YAML block has none of those provenances; it's LLM output dressed up as configuration. Even if it parses, the user can't verify whether it matches a real shipped rule's desc or silently no-ops because of a typo.
For the tuning conversation, do this instead:
shipped rule (or new rule) it would affect. sponsio plugin show <id> prints every rule currently loaded; read that output and quote a desc verbatim if you need to refer to one — but do NOT compose YAML around it.
(~/.sponsio/plugins/<id>/sponsio.yaml) and describe the change in words: "add a customized: entry beside the agent's contracts: list whose match.desc is the rule you want to silence, with disabled: true", or "append a new contract to the agent's contracts: list". Point them at the existing pack's syntax for reference; let them author the YAML themselves.
sponsio validate --config ~/.sponsio/plugins/<id>/sponsio.yaml and help them debug if it doesn't parse.
The reason for not ghostwriting: an injected prompt can slip a malicious-but-plausible snippet into a "helpful" agent suggestion, and a trusting user could apply it. Keeping the human as the only author of the bytes is the privilege boundary that makes Sponsio's guarantees real.
If the user has no idea what numbers to use, suggest:
export SPONSIO_GUARD_MODE=observeRun their normal workflow for a day, then come back and:
sponsio report --since 24hSurface the would-have-blocked rules. For every cluster of legitimate-looking violations, tighten the matching customized:. This is the data-driven counterpart to the questionnaire above — the questionnaire is the cold-start prior, the report is the posterior.
Run a synthetic event through the hook command:
echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' \
| sponsio plugin guard --stdinExpect: a JSON deny payload on stdout. If it's empty:
~/.sponsio/plugins/_host/sponsio.yaml is wrong orwasn't written. Re-run sponsio plugin init --force.
Update with pip install -U sponsio.
For an unbundled plugin you scanned in step 3b, do the same with one of its tool names plus a pattern you expect to block.
If the user is in a live Claude Code session, the plugin needs to re-pick up the manifest. Tell them to run /reload-plugins and confirm it shows sponsio-claude-code.
Operator wants observe mode (log, don't block) — pilot rollout:
export SPONSIO_GUARD_MODE=observeThis dial only affects this plugin; other Sponsio integrations in the same shell still respect SPONSIO_MODE independently.
Operator wants per-plugin overrides instead of editing the library:
Add an customized: block under the agent in ~/.sponsio/plugins/<plugin>/sponsio.yaml:
agents:
github:
contracts: [...shipped...]
customized:
- match: { desc: "delete_repository is blocked outright (customized: disabled: true to allow)" }
disabled: true"My rule looks right but the deny doesn't fire."
Most common causes:
mcp__github__delete_repobut the actual tool is mcp__github__delete_repository. The stateless hook silently no-ops on non-matches (correct behaviour — we can't deny on a tool we have no rule for). Confirm the real name from claude mcp list or by triggering the tool once and reading the PreToolUse event from ~/.claude/projects/.../*.jsonl.
~/.sponsio/plugins/<routed-id>/sponsio.yaml where the routed id matches what derive_plugin_id would return for the tool name. mcp__github__X → github; acme:fetch → acme; bare Bash → _host.
stateless hook gets a fresh empty trace per fire. Until the daemon mode lands, only argument-level rules (arg_blacklist, arg_value_range, scope_limit, arg_length_limit, dangerous_*, tool_allowlist) reliably fire on a single call. rate_limit, loop_detection, must_precede, cooldown are all daemon-future.
which starter libraries they want — don't push the whole bundle.
them you can't safely scan and suggest they list MCP tools from a live Claude session first.
--force to overwrite a user-edited library withoutexplicit consent.
sponsio/contracts/*.yaml inside theinstalled Sponsio package. Those are read-only shipped packs. User-level adjustments go in ~/.sponsio/plugins/<plugin>/sponsio.yaml.
SPONSIO_GUARD_MODE=observe "to make the smoketest pass". A failing smoke test means something is actually broken; silencing it hides the bug.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.