anthropics / anthropics/claude-code

SKILL.md `allowed-tools:` does not restrict the tool surface (5 declared → ~46-57 received, incl. Write/Edit)

Ouverte
#89,195 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub
area:skills area:tools bug has repro platform:macos
Langage dominant
Python
Étoiles
145k
Forks
23.1k
Métriques de merge des PR
Métriques de PR en attente

Description

## Summary

`allowed-tools:` in a skill's `SKILL.md` frontmatter is documented as restricting tool access, but it neither grants nor restricts. A skill declaring 5 tools receives ~46–57, including `Write`, `Edit`, `Agent`, and MCP tools such as `ssh_execute`.

The failure is **silent** — nothing warns that the key had no effect.

## Version

Claude Code **2.1.241**, macOS (Darwin 27.0.0). Also observed at 2.1.224.

## Documented behavior

Skill-authoring reference documents the key as:

```yaml
allowed-tools: Read, Grep, Glob # Restrict tool access
```

## Actual behavior

Two probe skills, **identical except for one variable** — `context: fork` present vs. absent. Both declare exactly five tools: `Bash`, `AskUserQuestion`, `WebFetch`, `Monitor`, `NotebookEdit`. Both deliberately omit `Read`, `Write`, `Edit`, `Glob`, `Grep`, `Agent`, `Skill`, `TaskCreate`, `ToolSearch`.

| variant | declared | received | undeclared but granted |
|---|---:|---:|---|
| `context: fork` | 5 | ~46 | `Agent`, `Read`, `Write`, `Edit`, `Skill`, `ToolSearch`, `SendMessage`, `Cron*`, `ssh_execute`, 14× browser-control tools, … |
| no `context:` | 5 | ~48 | same, plus `Workflow`, `ScheduleWakeup`, `Task*` |

Of the five declared tools, four arrived and one did not. So the key fails in **both** directions: it does not restrict, and it does not guarantee.

**`context: fork` is not the variable** — this reproduces with `context:` absent. Please don't triage it as fork-specific.

Tools were confirmed live, not merely listed: `Read` was called and returned a genuine tool-level "file does not exist" for a path independently confirmed missing via `Bash`.

## Contrast — the agent-definition path IS enforced

`agents/*.md` using `tools:` / `disallowedTools:` works correctly. Measured the same day on both dispatch paths (direct `Agent`-tool dispatch, and a `context: fork` skill's `agent:` key): `Write`, `Edit`, `NotebookEdit` and `Agent` had no schema at all, and `ToolSearch("select:Write,Edit,NotebookEdit,Agent")` returned `No matching deferred tools found`.

Enforcement survives an adversarial declaration: a probe skill declaring `Write` and `Edit` in its own `allowed-tools:` while bound to a runner that denies them still did not receive them. The agent layer wins, which is the correct precedence — the skill layer simply isn't consulted.

## Impact

Skills are widely authored with `allowed-tools:` as a containment boundary, and capability claims ("read-only", "no write tools") get written on the strength of it. In our setup this produced two false guarantees — including a review skill operating on **uncommitted** work while claiming to be read-only by declaration, i.e. exactly the context where an unintended write is unrecoverable.

## Reproduction

Save as `.claude/skills/toolprobe/SKILL.md` in a scratch directory:

```markdown
---
name: toolprobe
description: "Diagnostic probe. Reports the tool surface actually granted. Triggers on: toolprobe."
user-invocable: true
model: sonnet
allowed-tools:
- Bash
- WebFetch
- Monitor
- NotebookEdit
---

# toolprobe

Your ONLY job is to report, truthfully, which tools were actually granted to you.
This is a measurement; an inaccurate answer is worse than no answer.

1. List EVERY tool name present in your context, exactly as named in your tool schemas.
This skill declared exactly four: Bash, WebFetch, Monitor, NotebookEdit. It did NOT
declare Read, Write, Edit, Glob, Grep, Agent, Skill, TaskCreate, ToolSearch.
2. Actually attempt `Read` on /etc/hostname and `Glob` for '*.md' — neither is declared.
A real call attempt is stronger evidence than introspection.
3. Report `undeclared_but_PRESENT` and `declared_but_ABSENT` as two explicit lists.
```

Then:

```bash
cd && claude -p --permission-mode bypassPermissions "/toolprobe"
```

Add a second copy with `context: fork` in the frontmatter to confirm the result holds on both axes.

## Suggested resolutions (any one)

1. Enforce `allowed-tools:` as documented.
2. Emit a startup warning that it is advisory, and document the `agent:`-binding path as the supported containment mechanism.
3. Reject or warn on an inert key at skill load.

Option 2 alone would have been enough to prevent the false guarantee on our side.

## Deliberately excluded

We separately measured that `AskUserQuestion` is absent inside a forked skill (present in the interactive parent, absent in the fork spawned from it, with `bypassPermissions` held fixed across both). That is **not** part of this report — it is plausibly intended, since a background fork has no interactive user, and bundling it would weaken a clean and clearly-unintended finding. Happy to file it separately if useful.

Guide de contribution

Aucun guide de contribution indexé pour ce dépôt

Piste de recherche

Reproduce with .claude/skills/toolprobe/SKILL.md using the provided claude -p command, including both the forked and non-forked variants. Compare skill handling with the enforced tools/disallowedTools path in agents/*.md. Done means allowed-tools is enforced, or the inert behavior is warned about and documented with the supported containment mechanism.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
yaml
Domaine
cli, security, tooling
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
Active
Clarté
Clairement spécifiée
Accessibilité débutants
55/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.