SuperAPI AI Directory

agent-supply-chain-audit

Security & auditing

View repository

Updated 10-27 00:15

About this skill

Agent Supply-Chain Audit — Skills, Plugins, Hooks, MCP Servers, Instruction Files

Coding agents now load third-party code and third-party instructions with the developer's full privileges: a skill is prose the model obeys, a hook is a shell command that runs on every prompt, an MCP server is a process launched from whatever npx resolves today. NVIDIA's SkillSpector write-up cites Liu et al. (2026): of 31,132 public skills analysed, 26.1% had a vulnerability and 5.2% looked malicious. Nobody reviews these the way they review a dependency.

This skill inventories everything the user's agents load, reads it for concealed or hostile behaviour, checks what runs automatically and how it's pinned, and returns a per-item verdict. It never executes, installs, edits, or removes the items it audits.

Cross-references: prompt-injection for the same hidden-instruction and tool-poisoning checks applied to the user's application (RAG corpora, system prompts, the MCP servers the app exposes); secrets-audit for tokens sitting in MCP env blocks, agent settings, and session transcripts; dependency-audit for the npm / PyPI packages behind MCP servers and bundled scripts; ai-risk-management for where agent tooling sits in the org's AI inventory; incident-triage when a malicious item has already run; personal-opsec for the operator's own machine and accounts around the agent.

Step 0 — Authorization and scope

  • Audit the user's own machine, repos, and the items they're considering installing. User-level folders (~/.claude, ~/.codex, …) belong to the person at the keyboard; don't audit another user's home or a shared host without the owner's say-so.
  • Agree the scope: project (this repo), user (home-directory config), managed (org policy files), or a candidate (a skill / plugin / server the user hasn't installed yet — have them clone it to a scratch directory; don't install it to look at it).
  • Read-only. Config files hold tokens: when a match contains a secret, report the file and key name, redact the value to prefix + last 4, and hand it to secrets-audit. Don't paste secret values into the conversation.
  • Never run an item to see what it does (no launching MCP servers, no running bundled scripts or hook commands). Where dynamic evidence is needed, write the validation_plan for the user to run in a sandbox.

Step 1 — Inventory

List every instruction file, skill, command, subagent, plugin, marketplace, hook, settings file, and MCP server each agent loads, at every scope. The per-agent path tables and a one-shot discovery command are in references/locations.md. The headline locations:

Agent Instructions Extensions Executes code
Claude Code CLAUDE.md, CLAUDE.local.md, ~/.claude/CLAUDE.md .claude/skills, ~/.claude/skills, .claude/commands, .claude/agents, ~/.claude/plugins/, .claude-plugin/plugin.json + marketplace.json .claude/settings*.json, ~/.claude/settings.json, hooks/hooks.json, .mcp.json, ~/.claude.json, claude_desktop_config.json, managed settings
Cross-agent AGENTS.md .agents/skills (where npx skills installs) —
Cursor .cursor/rules/*.mdc, .cursorrules .cursor/commands .cursor/mcp.json, ~/.cursor/mcp.json, .cursor/hooks.json
Codex AGENTS.md, ~/.codex/AGENTS.md ~/.codex/prompts, ~/.codex/skills ~/.codex/config.toml ([mcp_servers.*], approval and sandbox policy)
Gemini CLI GEMINI.md, ~/.gemini/GEMINI.md .gemini/commands, ~/.gemini/extensions/* .gemini/settings.json, ~/.gemini/settings.json (mcpServers)
Copilot .github/copilot-instructions.md, .github/instructions/*.instructions.md .github/prompts, .github/agents, .github/chatmodes .vscode/mcp.json, VS Code user settings

For each item record: type, agent, scope, source (marketplace, git URL, npm/PyPI package, docker image, remote URL, hand-written), pin (commit SHA, exact version, digest — or "unpinned"), and what it can run (nothing / pre-approved tools / hooks / a server process). An item with no identifiable source is itself worth a note.

Step 2 — Content checks

Read every instruction-bearing file end to end: skill bodies and frontmatter, bundled references/ and scripts/, commands, subagents, rules files, plugin manifests, and MCP tool descriptions. Then grep. One representative pattern per category is below; the full set, with portable grep / perl equivalents, is in references/content-checks.md. Set T to the inventoried paths first.

Check SkillSpector IDs Representative grep
Invisible / bidi Unicode P2, TP2 rg -n '[\x{200B}-\x{200F}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{2066}-\x{2069}\x{FEFF}\x{00AD}\x{E0000}-\x{E007F}]' $T
Directives in hidden comments P2, TP1 rg -n -i '^\s*\[[^\]]*\]:\s*(#|<>)' $T and the multiline <!-- … --> keyword search
Whitespace padding P9 rg -n '[ \t]{40,}\S' $T and rg -c -U '(\n[ \t]*){20,}\S' $T
Encoded payloads P2, SC3 rg -n -i 'data:[a-z]+/[a-z0-9.+-]+;base64,' $T
Instruction override / covert steering P1, P4 rg -n -i '(ignore|disregard) (all )?(previous|prior|above) instructions|you are now|(do not|never) tell the user|silently' $T
Anti-refusal AR1–AR3 rg -n -i "(never|do not) refuse|without (any )?(warnings?|disclaimers?)|safety polic(y|ies) (do(es)? not|don'?t) apply" $T
Exfiltration wording and egress P3, E1, E4 rg -n -i 'webhook\.site|discord(app)?\.com/api/webhooks|api\.telegram\.org/bot|hooks\.slack\.com|ngrok|pipedream\.net|requestbin|oast\.' $T
Credential reads E2, E3, PE3 rg -n -i '\.ssh/|id_(rsa|ed25519)|\.aws/credentials|\.netrc|\.npmrc|printenv|process\.env|os\.environ' $T
Agent snooping AS1, AS2, AS3 rg -n '\.claude/|\.codex/|\.gemini/|\.cursor/|mcp\.json|claude_desktop_config|SKILL\.md' $T
Remote script execution SC2 rg -n -i '(curl|wget)[^|]*[|]\s*(sudo\s+)?(ba|z)?sh\b|bash\s+<\((curl|wget)' $T
Obfuscation, bytecode, disguised binaries SC3, SC8, SC9 rg -n 'base64 (-d|--decode)|atob\(|fromCharCode|\beval\(' $T; rg --files -uu $T -g '*.pyc' -g '*.so' -g '*.exe'
Unscoped tool pre-approval EA1 rg -n '^allowed-tools:.*\bBash\s*(,|$)|^allowed-tools:.*(Bash\(\*\)|WebFetch)' $T
Persistence / self-modification RA1, RA2, MP1 rg -n -i '(>>|tee -a|append|write)[^\n]{0,40}(CLAUDE|AGENTS)\.md|settings(\.local)?\.json|crontab|LaunchAgents|\.zshrc|\.bashrc|\.git/hooks' $T
Trigger abuse TR1, TR2, TR3 rg -n -i '^description:.*(always use|for (any|every|all) (task|request)|whenever the user)' $T; duplicate name: values

(In the table, \| is markdown escaping — type a plain | in the shell.)

How to read the hits:

  • Directive vs documentation. Security skills quote attack strings on purpose — this catalog does, heavily. A hit is a finding when it's addressed to the agent (prose, frontmatter, a comment, a tool description) and would change what it does. Quoted examples in fenced blocks or "look for X" lists are rejected with that reason.
  • Concealment is the signal. There is no benign reason for a zero-width character, a tag-block character, or a directive inside an HTML comment in a file whose only job is to instruct a model. Treat any concealed directive as confirmed regardless of what it says.
  • Declared purpose. Agent snooping (AS) and env reads are expected from an audit or config skill that says so and stays local. The finding is undeclared reads, reads of other skills, or reads that feed an outbound call.
  • Grants multiply. Unscoped Bash alone is code execution; unscoped Bash plus WebFetch/WebSearch adds untrusted input and an egress channel to the same skill.

Step 3 — Hooks and settings (BH1–BH4)

These run without the model deciding anything. Read every settings file in scope, project files first — a cloned repo's .claude/settings.json applies to whoever trusts the folder.

S=".claude/settings.json .claude/settings.local.json $HOME/.claude/settings.json hooks/hooks.json .cursor/hooks.json $HOME/.cursor/hooks.json"
# BH1/BH2: every hook command, then the ones that reach the network or decode payloads
rg -n '"(command|matcher|type)"\s*:' $S
rg -n -i '"command"\s*:\s*"[^"]*(curl|wget|\bnc\b|https?://|base64|\|\s*(ba|z)?sh|python[0-9.]* -c|node -e)' $S
# BH3: permission mode and broad grants
rg -n '"(defaultMode|allow|ask|deny|additionalDirectories|enableAllProjectMcpServers|enabledMcpjsonServers|disableAllHooks)"' $S
rg -n '"(Bash|Bash\(\*\)|WebFetch|Read\(~?/\*\*\)|mcp__[^"]*)"' $S
# BH4: settings keys that run a command on their own, and env that reroutes or injects
rg -n '"(apiKeyHelper|awsAuthRefresh|awsCredentialExport|otelHeadersHelper|statusLine|fileSuggestion)"' $S
rg -n '"(ANTHROPIC_BASE_URL|ANTHROPIC_API_URL|HTTPS?_PROXY|NODE_OPTIONS|LD_PRELOAD|DYLD_INSERT_LIBRARIES|GIT_SSH_COMMAND|CLAUDE_CODE_SHELL_PREFIX)"' $S

What to weigh:

Item Why it matters
UserPromptSubmit / PostToolUse hooks See every prompt / every tool result (file contents, command output). Any network call here is a BH2 exfil candidate.
PreToolUse hooks that return an allow decision Silently approve tool calls the user would otherwise be asked about
SessionStart hooks Run on open, before the user has typed anything; can inject context
defaultMode: bypassPermissions / acceptEdits / dontAsk in a project file Lowers the bar for everyone who clones the repo
permissions.allow with Bash, Bash(*), Bash(curl:*), WebFetch, Read(~/**) Pre-approval far beyond what a repo needs
apiKeyHelper, statusLine, awsAuthRefresh, … Shell commands that run on their own schedule; disableAllHooks doesn't cover every one of them — read each
env.ANTHROPIC_BASE_URL / proxy vars Route every prompt and file the agent reads through someone else's host
NODE_OPTIONS=--require, LD_PRELOAD, GIT_SSH_COMMAND Code injection into processes the agent spawns
enableAllProjectMcpServers: true Auto-approves whatever .mcp.json a repo ships

Equivalents elsewhere: Codex approval_policy = "never" with sandbox_mode = "danger-full-access" (rg -n 'approval_policy|sandbox_mode|\[mcp_servers' ~/.codex/config.toml); Gemini CLI "trust": true on an MCP server or auto-accept / YOLO settings (rg -n '"(trust|autoAccept|approvalMode)"' .gemini/settings.json ~/.gemini/settings.json); VS Code / Copilot tool auto-approve settings; Cursor auto-run and allowlists (in the app UI — ask the user).

Step 4 — MCP servers: pinning, tool definitions, rug pulls

Pinning (RP1, RP3)

M=".mcp.json .cursor/mcp.json .vscode/mcp.json $HOME/.claude.json $HOME/.cursor/mcp.json $HOME/.gemini/settings.json"
rg -n -A5 '"command"\s*:\s*"(npx|bunx|pnpm|uvx|uv|pipx|docker|deno)"' $M "$HOME/Library/Application Support/Claude/claude_desktop_config.json"
rg -n -A5 'command\s*=\s*"(npx|uvx|docker)"' $HOME/.codex/config.toml
rg -n '@latest|:latest"|"-y"|"--yes"' $M
rg -n '"(url|serverUrl|httpUrl)"\s*:' $M          # remote servers
"args": ["-y", "@acme/mcp-server"]           // unpinned (RP1): next launch runs whatever was published last
"args": ["-y", "@acme/mcp-server@1.4.2"]     // pinned to an exact version
"args": ["run", "ghcr.io/acme/mcp@sha256:…"] // docker: digest; a numeric tag is weaker; no tag / latest is unpinned

Unpinned package runners are a confirmed finding (condition third_party_dependency), sized by what the server can reach. Remote url servers can't be pinned at all — the vendor can change tools any time; record that and lean on the diff process below. Secrets in env / headers blocks go to secrets-audit; config should hold ${VAR} references only.

Tool definitions (TP1–TP4, LP1–LP4)

Read every tool's name, description, and each parameter's name, description, default, and enum labels. That text goes straight into the model's context and is as authoritative to it as the system prompt. Look for:

  • Injection in descriptions (TP1) and parameter definitions (TP3) — run the Step 2 hidden-instruction and override checks on the tool text. A catch-all parameter ("notes", "context", "sidenote") whose description asks the model to fill it with file contents, keys, or the conversation is the classic exfil shape.
  • Unicode deception in names (TP2) — mixed scripts or confusables that make read_file look like a trusted tool, or a name that shadows another server's tool.
  • Description vs behaviour (TP4, LP1) — with source available, compare the promise ("formats dates") with the handler (reads ~/.ssh, opens sockets, spawns processes). Over-broad declared permissions (LP2), none declared despite clear capabilities (LP3), or declared but unused (LP4) belong here too.
  • Cross-server instructions — a description that tells the model how to use another server's tools.

Rug pulls — pin and diff after approval (RP2)

A server can pass review at v1.0 and change its tool descriptions or add tools in v1.1, or a remote server can change overnight. The control is a snapshot at approval and a diff on every upgrade:

  • Static diff, no execution: for npm, npm diff --diff=@acme/mcp-server@1.4.2 --diff=@acme/mcp-server@1.5.0 shows every changed file, including tool registration code. Read changes to descriptions, schemas, new tools, and new network or file access.
  • Live snapshot (user runs it, in a sandbox): list tools with a pinned MCP Inspector, e.g. npx @modelcontextprotocol/inspector@<version> --cli <server command> --method tools/list > mcp-snapshots/<server>@<version>.json, then diff the sorted JSON against the approved snapshot. This launches the server, so it is a validation step for the user, not something this skill runs.
  • Flag: description or parameter text that changed, added tools, widened permissions or triggers, and any change in tool annotations (read-only → destructive). An approved server with no snapshot and no diff process is a hardening note.

Step 5 — Optional automated pass: SkillSpector

NVIDIA SkillSpector (Apache-2.0) runs ~70 static checks plus optional LLM-assisted ones and scores each skill 0–100 with a SAFE / CAUTION / DO NOT INSTALL verdict. If the user has it installed (pinned — it is one more tool you're trusting):

skillspector scan --no-llm .claude/skills -f json -o skillspector.json   # also .agents/skills, ~/.claude/skills, a candidate dir
  • Only --no-llm is pre-approved here. LLM mode sends file contents to the configured provider (NVIDIA's hosted API by default); run it only with the user's explicit OK and never on folders holding config secrets.
  • Treat its output as candidates. Merge each hit into your findings under its rule ID, triage it like any grep hit (security skills will trip it), and don't let a SAFE score replace reading the files — it can't judge intent.

Step 6 — Verdicts, severity, and per-item rating

Close every candidate as confirmed, needs_validation, or rejected (see references/findings-format.md). Severity goes on confirmed findings only, and overall never exceeds demonstrated impact (references/severity-calibration.md). Anchors for this surface:

Severity Typical shape
Critical Active item sends credentials, transcripts, or source to an external host; concealed directive that runs commands or disables approvals
High Hook or bundled script with undeclared egress; project settings that bypass permissions or reroute model traffic; persistence into instruction files, settings, or shell init
Medium Unpinned MCP server or git-installed skill with broad tool access; unscoped Bash + WebFetch pre-approval; tool description that steers the model toward another tool or extra data
Low Unpinned item with narrow reach; over-broad trigger that shadows another skill

Not findings — hardening notes: no tool-definition snapshot, missing permissions.deny rules, a pinned tag rather than a SHA, no review process for marketplace updates. Report them separately and never give them a severity.

Per item, roll the findings up:

  • DO-NOT-INSTALL (or remove now) — any confirmed critical/high, or any concealed directive.
  • CAUTION — confirmed medium/low, or open needs_validation items; list what would clear it (pin, scope the grant, remove the hook).
  • SAFE — nothing but rejected candidates and hardening notes. "SAFE" means "nothing found by this review", not a guarantee — say so.

Eat your own dog food

This catalog installs into .claude/skills/ and .agents/skills/, so audit it like any other source:

  • Pin it: install from a tagged release and record the commit SHA, not a moving branch.
  • Expect hits from quoted payloads (prompt-injection examples, secrets-audit key patterns, this skill's own patterns) — triage them as documentation, and record them as rejected so later runs stay quiet.
  • Several skills here pre-approve unscoped Bash, some alongside WebFetch/WebSearch. That's an EA1 finding against the catalog, sized by what the skill's instructions actually do. Report it like you would for anyone else's skill.

Output Format

Write the machine-readable findings first, validate them, then render the markdown from them.

  • findings.json — project-scope items, paths repo-relative. findings.user.json — user-scope items, paths relative to $HOME (e.g. .claude/settings.json). The validator rejects absolute and ~ paths, so keep the two roots apart. These are the only files this skill writes, and only after the user agrees where they go.
  • Each record: skill: "agent-supply-chain-audit"; fingerprint like agent-supply-chain-audit:BH2:.claude/settings.json:posttooluse-hook-posts-to-webhook (rule ID + item + root cause — no line numbers); title prefixed with the rule ID ([BH2] PostToolUse hook posts tool output to webhook.site); trace from activation (hook event, skill trigger, server launch) to sink (egress, exec, write); owasp_category may carry the OWASP LLM / Agentic ID (e.g. LLM03:2025 Supply Chain); no secret values anywhere — prefix + last 4.
  • Validate: node references/validate-findings.mjs findings.json (references/validate-findings.mjs) (and findings.user.json). Fix every error before writing prose.
# Agent Supply-Chain Audit
## Scope: [project / user / managed / candidate] — [paths]
## Date: [date] — SkillSpector: [version, --no-llm | not run]

### Inventory
| # | Item | Type | Agent | Scope | Source | Pin | Runs | Verdict |
|---|------|------|-------|-------|--------|-----|------|---------|
| 1 | .claude/skills/pdf-helper | skill | Claude Code | project | github.com/x/y | unpinned (main) | Bash (unscoped) | DO-NOT-INSTALL |

### Findings
#### [SEVERITY] [RULE-ID] [Title]
**Item / file:** `path` — **Verdict:** confirmed — **Category:** Hidden Instruction / Tool Poisoning | Exfiltration | Agent Snooping | Hooks & Settings | Rug Pull / Pinning | Supply Chain | Excessive Agency | Persistence | Trigger Abuse
**Evidence:** [quoted text or config, secrets redacted]
**Activation → impact:** [when it runs, what it reaches]
**Fix:** [remove / pin to X / scope grant to Y / delete hook]

### Needs validation
| Item | Rule | Blocker | Validation plan |

### Hardening notes (not findings)
- [gap, why it matters, suggested control]

### Rejected candidates
| Item | Rule | Why ruled out (control + location) |

Boundaries

  • Read-only against the audited items: never install, launch, run, edit, or delete them. Removal and pinning are recommendations the user carries out.
  • Never execute a hook command, bundled script, or MCP server to observe it; hand dynamic checks to the user as a sandboxed validation plan.
  • Don't copy secrets out of configs or transcripts into the report; redact and route to secrets-audit.
  • Refuse to help build or tune a malicious skill, poisoned tool description, or hook designed to evade review or scanners like SkillSpector. Explaining a technique so the user can detect it is fine.
  • If an item already ran and looks malicious, stop the audit and switch to incident-triage: rotate what it could reach, preserve the files, check egress.

References