Check S13 · agent surface family · severity CRITICAL · automatic · one of the 15 skill and plugin checks
The question this check asks: does this Claude Code plugin's hooks/hooks.json or monitors/monitors.json launch a command that fetches something and runs it? A hook runs on a tool call, a monitor on an event, and neither asks the model first. Whatever they launch is the part of a plugin that executes without anyone deciding to.
Claude Code's own documentation puts it in one sentence: “Plugins and marketplaces are highly trusted components that can execute arbitrary code on your machine with your user privileges.” A plugin carries its hooks in hooks/hooks.json, its monitors in monitors/monitors.json, and may declare either inline in .claude-plugin/plugin.json; a PreToolUse hook runs its command before every tool call it matches, a SessionStart hook when a session opens. The shape that turns that into a payload is the one C20 has read out of MCP and agent launcher configs since the first roster: a command that both fetches — curl, wget, Invoke-WebRequest — and executes what it fetched. Reversec's “Skill Issues” of 5 May 2026 showed the same idea in a skill's dynamic-context line, executed “before any checks”; a hook is the plugin-level version, and it runs on every matching call for as long as the plugin is installed. The record has no published malicious plugin in either Anthropic marketplace to name — the malicious skills page says so plainly — which is the reason to read the document now rather than after.
S13 runs on a plugin key. It fetches hooks/hooks.json and monitors/monitors.json when they exist, reads the hooks object inline in plugin.json when one is declared, parses the JSON, and reads every command a launcher would read — command plus its arguments, joined. A line fires when it both fetches and executes what it fetched: a pipe into a shell, an inline bash -c "curl … && ./x", or PowerShell's iex. That finding is mcp-exec:hooks/hooks.json or mcp-exec:monitors/monitors.json, CRITICAL, 30 points, and a BLOCK under the default profile — the same rule and the same code as C20, through the function the 22 use, and it quotes the offending line. Unparseable JSON yields nothing, deliberately: a launcher would not read it either, and guessing with a pattern over raw text is exactly the kind of misread the first measurement taught this roster to avoid.
Two further rows are notes, at 0 points, action allow, and they never move a verdict. skill-hook-every-call names a PreToolUse or PostToolUse hook whose matcher is empty, * or .* — a command on every tool call, which is what a formatter does. skill-hook-session-start names a SessionStart hook — a command when every session opens. Both are the declared surface, stated so the reader sees it beside whatever else fired; the false-positive principle that graded the earlier rosters holds here, because a plugin that declares a hook on every call is describing what it does, not hiding it.
Measured on 20 September 2026 through the same code as /v1/skill/score, over all 310 official-marketplace plugins and the first 100 community plugins: the shell-fetch shape fired on none; the two notes fired where formatters and session tools declare them and, being notes, moved no verdict. The roster's own fixture — a PreToolUse command hook that fetches and pipes to a shell — blocks. A fact from the Claude Code changelog the page may state as such: since 2.1.218 (22 July 2026), hooks declared in an agent file's frontmatter run only when that file's own folder has accepted workspace trust.
The one C20 records: a launcher line of the form npx -y some-package is a fetch and an execute in one word, and C20 keeps the class “under an instant red” on that account — here the shell-fetch shape needs the fetch and the execute to be distinct, so a bare npx does not fire on S13. A hook that legitimately updates itself — fetches a script from its vendor and runs it on every session — would fire, and should: the line is shown, and it is the same line a compromised plugin would carry. No plugin on either marketplace does this today. A hook that runs a binary the plugin ships is not this check's row at all; the binary is listed by S10.
What the hook's command does once it runs. S13 reads the line in the document; a command that launches a script from the plugin's own bin/ is read as a launch, and what that script does is S10's question if it was read, and the deep scan's if it was not. Hooks outside the plugin — in a user's or a project's settings — are not a listing and are not assessed; the agent check-up on the roadmap is where an installation's own hooks will be read. A bare skill has no hooks document, and a plugin without one has nothing that runs at install, so an absent document is a pass, not a skip. ClawHub (@owner/slug) is parsed and routed but not assessed on this tier — and a ClawHub skill is never a plugin, so this check has nothing to run there in any case.
Scope, the same on every page of this roster: RepoGates does not see git clone, package managers or curl — outside Claude Code with the RepoGates plugin, whose hook refuses a clone or install that names a blocked repository on the command line, before it runs. The hook sees Bash tool calls in that one client and nothing else: npx skills add owner/repo and claude plugin install name@marketplace are shell lines it judges; /plugin install is a slash command the hook never sees; a skill copied by hand into ~/.claude/skills is never seen at all. This tier reads the listing record, the tree once, and at most twelve files of 64 KB each — SKILL.md files first, then a plugin's agents and commands, then the scripts beside each skill; more than fits is a partial scan, stated, and never a pass on what was not read. The deep scan reads the whole package. A PASS means nothing matched on what was read, never that the skill was run.
Open the plugin's hooks document and read every command in it. Claude Code runs each one with your user privileges on the event it names, and a hook with no matcher runs on every tool call:
jq -r '.. | .command? // empty' hooks/hooks.json monitors/monitors.json 2>/dev/null
jq -r '.hooks // empty | .. | .command? // empty' .claude-plugin/plugin.json
A command that contains curl or wget and a pipe into sh or bash is the shape this check blocks on. A command that runs a file from the plugin's own bin/ is one to open next.
Why is a hook on every tool call only a note? Because that is what a formatter does. A PreToolUse or PostToolUse hook with no matcher runs a command on every tool call, and a plugin that formats every file an agent writes needs exactly that. The shape says the plugin declares it; it does not say the command is hostile. So the note names it, the reader sees it beside whatever else fired, and the one thing that makes a hook a finding on its own — a command that fetches something and runs it — is critical and blocks. A SessionStart hook is the same: a command when every session opens, listed, not graded.
What does the check do with a hooks.json it cannot parse? Nothing, deliberately — the rule it borrows from C20. A launcher that cannot parse the document will not run its commands either, and guessing at commands with a pattern over raw text would produce the misreads the first measurement taught this roster to avoid. What is parsed is the hooks document as a launcher reads it: every event, every entry, the command and its arguments joined, and the matcher. Hooks declared inline in .claude-plugin/plugin.json are read the same way.
Did any marketplace plugin fire this check? Not above a note. On 20 September 2026 the shell-fetch shape fired on none of the 310 official-marketplace plugins and none of the first 100 community plugins; the every-call and session-start notes fired where formatters and session tools declare them, and a note never moves a verdict. The roster's own fixture — a PreToolUse command hook that fetches and pipes to a shell — blocks, which is what the check exists to do on the day a plugin ships one.
C20 MCP shell launch — the rule and the code this check reuses, on a repository's launcher configs · S14 MCP servers — the same rule on the plugin's .mcp.json, the other document that starts with the session · S6 Declared tool surface — the Bash(*) grant and the agent that bypasses its prompt · S9 Instruction shapes — the dynamic-context line, the skill-level version of a command that runs before the model reasons.
Add RepoGates to Chrome Pricing
Numbers on this page: the 20 September 2026 measurement of all 310 official-marketplace plugins and the first 100 community-marketplace plugins, run through the same code as /v1/skill/score; the Claude Code plugins reference and changelog (2.1.218, 22 July 2026); Reversec Labs, 5 May 2026; the incident record behind the malicious skills page; thresholds and weights from the product's policy and scoring tables.