Audit the extensions AI coding agents load and trust (skills, plugins, marketplaces, hooks, settings, MCP servers, and instruction files like CLAUDE.md, AGENTS.md, GEMINI.md, .cursor/rules and copilot-instructions.md) across Claude Code, Cursor, Codex, Gemini CLI and GitHub Copilot. Read-only: inventories each item with its source and pin, greps for hidden instructions, exfiltration, credential reads, persistence, curl|bash and over-broad tool grants, reviews hooks and MCP pinning and tool poisoning, and gives each item a SAFE / CAUTION / DO-NOT-INSTALL verdict. Use when the user mentions 'audit my skills,' 'is this skill malicious,' 'is this skill safe,' 'is this MCP server safe,' 'review CLAUDE.md,' 'review AGENTS.md,' 'plugin security,' 'check my hooks,' 'audit my MCP servers,' 'agent supply chain,' 'SkillSpector,' 'tool poisoning,' 'rug pull,' or is about to install a skill, plugin, or MCP server.
96%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
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;
cicd-security for agents that run inside CI workflows (claude-code-action and similar).
~/.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.secrets-audit. Don't paste secret values into the conversation.validation_plan for the user to run in a sandbox.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.
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:
rejected with that reason.Bash alone is code execution; unscoped Bash plus WebFetch/WebSearch adds untrusted
input and an egress channel to the same skill.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)"' $SWhat 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).
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 unpinnedUnpinned 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.
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:
read_file look like a trusted tool, or
a name that shadows another server's tool.~/.ssh, opens sockets, spawns processes). Over-broad declared permissions (LP2), none declared despite
clear capabilities (LP3), or declared but unused (LP4) belong here too.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:
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.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.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--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.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:
needs_validation items; list what would clear it (pin, scope the grant,
remove the hook).rejected candidates and hardening notes. "SAFE" means "nothing found by this review", not a
guarantee — say so.This catalog installs into .claude/skills/ and .agents/skills/, so audit it like any other source:
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.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.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.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.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) |secrets-audit.incident-triage: rotate what it could
reach, preserve the files, check egress.briiirussell/cybersecurity-skills
c79bb8b
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.