anthropics / anthropics/claude-code
[BUG] Auto-mode catastrophic-removal guard bypassed: `rm -rf` inside a backtick substitution executes without a prompt
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
### Preflight Checklist
- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
### What's Wrong?
In auto permission mode the Bash tool executed an effective `rm -rf /*` with no permission prompt.
The destructive command was never the command being run. It was text inside a double-quoted
`python -c "..."` argument — a data file was being edited, and the value being written happened to
contain a backticked `rm -rf $UNDEFINED_VAR/*` as a documentation example. Bash parsed that argument
before Python received anything: backticks inside double quotes are command substitution, so the span
was executed; `$UNDEFINED_VAR` expanded to empty; the effective command became `rm -rf /*`.
On Windows the Bash tool is backed by Git for Windows, where `/` maps to the Git installation root.
The result was a recursive delete of that installation (`usr/bin` including `bash.exe`, `cmd`, `etc`,
`mingw64`). Git had to be reinstalled. The delete also destroys the agent's own shell mid-run, so it
cannot investigate or clean up afterwards.
Two safeguards that target exactly this were already present in the build:
- v2.1.205 — "Improved auto mode to ask before running `rm -rf` on a variable it can't resolve from context"
- v2.1.208 — "Catastrophic removals (e.g. `rm -rf ~`) in commands containing `$(…)`/backticks/`<(…)` now
prompt in `--dangerously-skip-permissions` and auto mode"
The command matched BOTH conditions — a recursive `rm` against an unresolvable variable, and backticks.
Neither fired. No prompt was shown.
### What Should Happen?
Auto mode should have prompted (or refused) before executing anything inside the substitution span.
Per the v2.1.208 note the prompt is supposed to apply even under `--dangerously-skip-permissions`, so no
permission setting should be able to suppress it.
Concretely, the classifier should evaluate what the shell will actually run, not only the surface command:
extract the contents of `` `…` ``, `$(…)` and `<(…)` and apply the same catastrophic-command checks to each
span. A recursive `rm` whose target contains a variable cannot be shown safe from the command text alone —
an unset variable silently reduces the target to the filesystem root.
### Error Messages/Logs
No permission prompt was emitted.
**What the `rm` output shows — and what it does not.** `rm` is silent on success, so there is no
line showing a successful deletion. What the output proves is that `rm -rf` was invoked against the
filesystem ROOT: it enumerated the virtual root and reported only the paths it could NOT remove. It
ran until the tool timeout (exit 143, "Command timed out after 2m 0s")
### Steps to Reproduce
1. Run Claude Code on Windows with the Bash tool backed by Git for Windows, and
`permissions.defaultMode: "auto"`.
2. Ask it to write a text value that itself CONTAINS a shell example with backticks and a variable —
e.g. editing a tips/docs file whose content includes `` `rm -rf $UNDEFINED_VAR/*` ``.
3. If the agent inlines that text into a `python -c "..."` (or any `-c` payload) argument to the Bash
tool, bash evaluates the backticked span before the interpreter is invoked.
This harmless command demonstrates the same evaluation, and is safe to run:
```bash
python -c "x = '`echo SUBSTITUTED`'; print('python received:', repr(x))"
```
Verified output:
```
python received: 'SUBSTITUTED'
```
Python received `SUBSTITUTED` — the RESULT of running the substituted command — not the literal
characters `` `echo SUBSTITUTED` ``. That is the whole mechanism: bash evaluated the backtick span
before Python was invoked. Replacing `echo SUBSTITUTED` with a recursive removal against an unset
variable is exactly what occurred, and it produced no prompt.
### Claude Model
Opus
### Is this a regression?
I don't know
### Last Working Version
_No response_
### Claude Code Version
v2.1.219
### Platform
Anthropic API
### Operating System
Windows
### Terminal/Shell
VS Code integrated terminal
### Additional Information
Both guards appear to reason about the command's **structure**, and structurally this command is a
Python invocation:
```
cd && python -c ""
```
Split into commands, the executables are `cd` and `python`. The `rm -rf …` never appears as a command
being invoked — it is characters inside a single quoted argument. So:
1. The **v2.1.205** check ("`rm -rf` on a variable it can't resolve") never sees an `rm` invocation to
evaluate at all.
2. The **v2.1.208** check is conditional on first identifying a *catastrophic removal*. Since step 1
found none, the "does this command contain `$(…)`/backticks" condition is never reached — even
though the command plainly contains backticks.
The gap is that neither guard descends **into** the backtick span. But the shell evaluates that span
first and independently of the outer command: bash runs the backticked text as its own command before
`python` is ever executed. A guard that models shell evaluation order would see an `rm -rf` here; one
that inspects the surface command will not.
A secondary factor: even when descending, `$UNDEFINED_VAR/*` still has to be classified as
catastrophic. The v2.1.208 example is `rm -rf ~`, a literally-known dangerous path.
`$UNDEFINED_VAR/*` only becomes `/*` after an unset variable expands to nothing — exactly the case
v2.1.205 was meant to cover.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by tracing the Bash permission classifier and the v2.1.205/v2.1.208 catastrophic-removal checks; no source file or test path is named. Reproduce the safe Python backtick example, then add coverage for commands inside backticks, $(…), and <(…) with unresolved variables; done means the dangerous nested command prompts or is refused.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bash, python
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100