Declared tool surface

Check S6 · agent surface family · severity HIGH · automatic · one of the 15 skill and plugin checks

The question this check asks: what does this skill or plugin declare it may do without asking? A skill's frontmatter can carry an allowed-tools line — the tools Claude may use without a permission prompt for the turn the skill runs in. A plugin's agent file can carry permissionMode. Both are declarations, written by the author, read before anything runs. This check reads them and says what they grant.

Why it matters

James Henderson's “Skill Issues” post for Reversec Labs, 5 May 2026, is the record here. Without frontmatter a skill's commands go through the user's consent when the prompt shows; the post's finding is that “with the allowed-tools header, suddenly skills can execute commands”, and the user is not shown that they are executing. The demonstration paired allowed-tools: Bash(*) with a dynamic-context line that opened a shell relay — the line itself is S9's shape; the grant that let it run without a prompt is this check's. On agents, the same post: an agent definition carrying permissionMode: bypassPermissions “did not prompt before running arbitrary bash commands despite my settings”. The defence the post records as holding was a settings rule denying Bash, which applied whatever the skill file said. Neither part of the post records a disclosure or a response, so what changed in response is not verified; what can be stated are Anthropic's own documents and changelog, dated by release. The skills reference warns, verbatim, “Workspace trust doesn't gate allowed-tools” and tells the reader to review the allowed-tools of skills checked into a repository before running Claude Code there. Changelog 2.1.223 (6 August 2026) closed a gap where an agent definition's bypassPermissions ignored the organisation's bypass-permissions disable policy; 2.1.257 (1 September) made a defaultMode: "bypassPermissions" in project settings ignored; the sub-agents reference today says a subagent runs in that mode only when the main conversation does. None of that removes the line from the file, and the file is what an installer copies. The malicious skills page has the record.

How RepoGates scores it

S6 reads two things. The first is each SKILL.md's frontmatter, parsed with PyYAML's safe_load in the app layer: allowed-tools (also allowed_tools and allowedTools), a list or a space- or comma-separated string, each token a tool name with an optional scope. Six tokens are wide: an unscoped Bash, Bash(*), Bash(*:*), WebFetch, Write and Edit. A scoped grant — Bash(git:*), Bash(jq:*), Read — is not noted. Each wide token becomes one row, skill-tools:<tool>, collected across every skill under the key. On an established listing — a repository 90 days old or older, the roster's skill_established_days — every such row is INFO at 0 points, reading “declares Bash(*) in allowed-tools — a declared surface; noted”. On a listing younger than that, a single wide token is still a note; two or more together are MEDIUM at 5 points, “a wide surface on a listing too new to have earned it”. Even at MEDIUM the default profile's action for skill-tools is allow: a declared surface is a note, the same principle that keeps a declared host binary a note on a Docker Desktop extension. Nothing the profile does turns this row into a refusal on its own.

The second is a plugin's agents/*.md. The leading --- block of each is read by one regular expression — not a parser; PyYAML never sees an agent file on this tier — for a permissionMode line set to bypassPermissions. A hit is skill-permission-mode:bypassPermissions, HIGH at 12 points, a REVIEW the profile warns on: “permissionMode bypassPermissions — the subagent runs every tool without a prompt (Reversec, 2026-05-05)”, with the file paths in front of it, three at most. This row is never ordinary. An established listing does not downgrade it, because no documented use of a plugin agent needs the prompt removed for every tool.

Measured on 20 September 2026 through the same code as /v1/skill/score, the MEDIUM row fired on 1 of the 189 skills.sh keys and 1 of the 310 official-marketplace plugins, on none of the first 100 community plugins and none of the malicious set. The skills.sh row is 101-skills/superpowers, a listing 19 days old whose report is a REVIEW on S1 and S2 before this row is counted. The official row is ckeditor, which declares Write, Edit and Bash on a listing younger than 90 days — graded as designed, and allowed. The HIGH row fired on nothing measured; the test suite holds a fixture reconstructed from Reversec's post, an agent file with the line and an exfiltration instruction in its body, at REVIEW or worse.

False positives we know about

The flagship skills. A skill that formats code declares Edit; one that deploys declares an unscoped shell; one that researches declares WebFetch. That is why the row is a note on an established listing and an allowed MEDIUM on a new one — the measurement's two MEDIUMs are a skills collection nineteen days old and an editor vendor's plugin, and neither is a fault. What would make the grant matter is a line in the SKILL.md or a script that uses it badly, and those are read by S9 and S10.

An agent file that sets bypassPermissions on purpose, for an author who runs it inside a sandbox. The row fires, because the line is on the file whoever installs it; the finding names the path so the reader can see which agent, and an override records the reason. No false fire of this row was observed on any set.

What this check does not cover

What the agent does with the grant. S6 reads the declaration. The dynamic-context line that turns Bash(*) into a shell is S9's shape; a script that uses the grant is S10's. Any other agent frontmatter: a tools list, disallowedTools, an agent file's own hooks block, and a permissionMode other than bypassPermissions are not read here. An agent file outside the plugin — a repository's .claude/agents/ or the user's own ~/.claude/agents/ — is not on this tier's read list. What the client enforces. The changelog facts above say what Claude Code does with the line today; the check grades the declaration, not the client's behaviour, because the file outlives any one version. ClawHub (@owner/slug) is parsed and routed but not assessed on this tier: every text check, S6 among them, is a listed skip there until the fetch ships, and a ClawHub key as a whole answers “not assessed” — never a pass.

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.

Check it yourself

The grant is in the first lines of the file, and the agent line is a single key. On a checkout of the skill or plugin:

sed -n '/^---$/,/^---$/p' SKILL.md | grep -n 'allowed-tools'
grep -rn 'permissionMode' agents/ 2>/dev/null

An unscoped Bash, a Bash(*), WebFetch, Write or Edit in the first is the surface this check notes; bypassPermissions in the second is the warning. Then read what the skill does with it. The setting Reversec's post records as holding — a rule in your own Claude Code settings that denies Bash — applies regardless of what any skill file declares, and the skills reference's own advice stands: review the allowed-tools of every skill checked into a repository before running Claude Code in it.

Questions

Why is Bash(*) only a note on most skills? Because a declared surface is what the author says the skill will use, and the flagship skills say it: a formatter that writes, a research skill that fetches, a build skill that runs a shell. On a listing 90 days old or older each wide tool is a note at 0 points; on a listing younger than that, two or more together are MEDIUM at 5 points, and even then the profile's action is allow. The check states the surface so a reader sees it beside what the skill then does — which is S9's question for the instructions and S10's for the scripts. A scoped grant such as Bash(git:*) is not noted at all.

What does permissionMode: bypassPermissions in an agent file mean? That the plugin's subagent is declared to run every tool without a prompt. Reversec's post of 5 May 2026 recorded an agent with that line running arbitrary bash commands with no prompt despite the author's own settings. RepoGates grades the line HIGH at 12 points, a warning, and never downgrades it for an established listing. Claude Code's sub-agents reference today says a subagent runs in that mode only when the main conversation does, and changelog 2.1.223 of 6 August 2026 closed a gap where an agent's bypassPermissions ignored the organisation policy — changelog facts, listed as such. The declaration is still on the file, and the check reads the file.

How is the allowed-tools line read? The SKILL.md frontmatter is parsed with PyYAML's safe_load in the app layer; allowed-tools — also allowed_tools and allowedTools — may be a list or a space- or comma-separated string, and each token is read as a tool name with an optional scope in brackets. A frontmatter that does not parse is S5's unparsed finding, and S6 then has nothing to read. The agent file is different: one regular expression on the leading front-matter block, looking for a permissionMode line set to bypassPermissions. It is the one YAML-shaped read this check makes without a parser, and it is deliberately narrow.

Related checks

S9 Instruction shapes — the dynamic-context line that turns a declared grant into a shell · S7 Declared requirements — the other declaration read from the same frontmatter, a note for the same reason · C18 Agent config files — the repository-level twin: the files an agent auto-loads on a GitHub repository · S13 Hooks and monitors — a plugin's other surface that runs before a prompt.

Add RepoGates to Chrome Pricing

Numbers on this page: the 20 September 2026 measurement of 189 skills.sh keys, all 310 official-marketplace plugins, the first 100 community-marketplace plugins and the named incidents, run through the same code as /v1/skill/score; Reversec Labs (James Henderson), “Skill Issues”, 5 May 2026; Anthropic's Claude Code changelog (2.1.223, 6 August 2026; 2.1.257, 1 September 2026) and the skills and sub-agents references as read on 20 September 2026; the incident record behind the malicious skills page; thresholds and weights from the product's policy and scoring tables.