anthropics / anthropics/claude-code

[BUG] Auto-mode catastrophic-removal guard bypassed: `rm -rf` inside a backtick substitution executes without a prompt

Open
#81,273 1 comment 0 reactions 0 assignees View on GitHub
area:permissions bug
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.