anthropics / anthropics/claude-code

[BUG] Disabling the Bash tool (--disallowedTools Bash, permissions.deny: ["Bash"]) leaves Monitor executing arbitrary shell, while per-command Bash(...) rules do apply to it

Open
#95,615 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area:permissions area:security bug has repro platform:macos
Dominant language
TypeScript
Stars
146k
Forks
23.8k
PR merge metrics
PR metrics pending

Description

Summary

Monitor.command runs through the shell — the tool description says "The script runs in the same shell environment as Bash."

Per-command Bash permission rules are applied to it. With permissions.deny: ["Bash(touch:*)"], a Monitor call is refused, and the message names Bash:

Permission to use Bash with command touch /tmp/.../MONMARKER; echo done has been denied.

But disabling the Bash tool is not applied to it. With --disallowedTools Bash or permissions.deny: ["Bash"], Bash disappears from the tool list and Monitor.command runs anything, unchecked.

The same restriction expressed two ways gives opposite results. The weaker-looking form (a narrow command pattern) is enforced; the stronger-looking form (remove the tool entirely) is not.

Reproduction

Version 2.1.278, macOS 26 (Darwin 25.6.0). Default permission mode in all three cases.

1. Per-command deny — enforced (correct behaviour):

T=$(mktemp -d)
echo '{"permissions":{"deny":["Bash(touch:*)"]}}' > "$T/deny.json"
claude -p "Use the Monitor tool with command: touch $T/MONMARKER; echo done \
and timeout_ms 5000. Report verbatim what it returned." \
  --settings "$T/deny.json" --max-turns 6
ls "$T"

Result: denied, no MONMARKER.

2. Whole-tool disallow — not enforced:

T=$(mktemp -d)
claude -p "First try the Bash tool to run: touch $T/BASHMARKER . \
Then use the Monitor tool with command: touch $T/MONMARKER; echo done \
and timeout_ms 5000. Report exactly what each returned." \
  --disallowedTools Bash --max-turns 8
ls "$T"

Result: MONMARKER exists, BASHMARKER does not. The model reports Bash is absent from its tool list, then performs the same touch through Monitor with no permission check.

3. Whole-tool deny rule — not enforced:

echo '{"permissions":{"deny":["Bash"]}}' > "$T/deny.json"
claude -p "...same prompt as 2..." --settings "$T/deny.json" --max-turns 8

Result: same as 2. MONMARKER created, Bash unavailable.

Restriction Bash tool Monitor command
deny: ["Bash(touch:*)"] denied denied
--disallowedTools Bash absent runs
deny: ["Bash"] absent runs

--permission-mode dontAsk denies the Monitor call outright, but that is dontAsk refusing anything that would prompt, not this check working.

Likely mechanism

The command-level evaluator is reached (it identifies itself as Bash in the denial message), but with the Bash tool removed there is no command pattern left to match, so the decision falls through to the mode default and allows. Tool-level enablement is checked against the calling tool's name — Monitor — and never against Bash.

Why it matters

The natural way to build a read-only headless wrapper is to drop the writing tools:

claude -p --disallowedTools "Bash,Write,Edit,NotebookEdit" ...

That is exactly the form Monitor ignores, so Monitor(command: "rm -rf ...") runs. Nothing signals the gap, because Bash genuinely disappears from the tool list and the restriction looks like it worked. Adding Monitor to the disallow list is the workaround, but the asymmetry is the bug — a user who has denied Bash has no reason to expect a watch-a-log tool to be the remaining shell.

Suggested fix

When the Bash tool is disabled by --disallowedTools or a whole-tool deny rule, refuse Monitor.command on the same grounds, the way per-command rules already do. The ws source is unaffected and can stay available.

Related

  • #88770 — permissions.deny Bash(curl *) silently bypassed under Auto Mode (under --permission-mode auto I see the Bash(touch:*) deny bypassed for both Bash and Monitor, consistent with this)
  • #92542 — Bash deny rules bypassed by wrapper programs
  • #78797 — deny rules not enforced in subagent shells (closed; --disallowedTools does propagate to subagents in 2.1.278, verified)
  • #91622 — --disallowedTools Bash matching the workspace shell via toolAliases

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing how Monitor.command reaches the command-level permission evaluator and how --disallowedTools Bash, permissions.deny, and toolAliases are handled. Run the three reproduction cases in the issue, checking Bash and Monitor separately. Done means disabling or denying Bash also blocks Monitor shell commands while leaving the ws source available.

Written by the indexing model from the issue text.

Assessment

Tech stack
shell, typescript
Domain
cli, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.