anthropics / anthropics/claude-code
PowerShell command blocked citing a path unrelated to the command; here-string text content is scanned as code
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
### Summary
When the built-in PowerShell safety check cannot statically parse a command (multi-line / compound scripts), it falls back to a string-splitting scanner. In that fallback path, "the first token that looks like a protected system path" and "whether a delete cmdlet appears anywhere in the script" are tracked as two **independent** variables spanning the entire multi-statement script. They are then combined at the end to produce a deny message.
Consequences:
1. The path shown in the error message can be completely unrelated to what the command actually deletes.
2. A command that deletes nothing at all can be blocked, if its **text content** happens to contain a delete-cmdlet name plus a token that normalizes to `/`.
3. Because the check is fail-closed and blocks the whole invocation, unrelated operations earlier in the same command (e.g. file writes) also never execute.
### Observed behaviour
**Case A — wrong path in message**
A multi-line PowerShell command that (a) read a file, (b) did a string replace, (c) wrote it back via `[System.IO.File]::WriteAllText`, (d) printed byte counts, (e) ran a `Select-String` probe, and (f) as its last statement deleted a lock file at a normal user path, was blocked with:
```
Remove-Item on system path '/' is blocked. This path is protected from removal.
```
The command contained no reference to `/`. The actual delete target was an ordinary file under a data directory. Verified that **nothing executed** (file byte count unchanged), confirming the block happens before execution.
**Case B — text content triggers it, with no delete operation present**
A later command performed a **pure write** — no delete of any kind — inserting a block of prose into a Markdown file using a single-quoted here-string (`@'...'@`). The prose was documentation *about* Case A, so it contained the cmdlet name as literal text, plus a quoted `'/'` inside a sentence. Result:
```
Remove-Item on system path ''/'」=**CLI' is blocked. This path is protected from removal.
```
The string in the message (`'/'」=**CLI`) is **a fragment of the prose being written** — the surrounding text is Chinese, hence the CJK characters. It is not a path, not an argument, and not part of any command. This demonstrates that here-string *content* is scanned as if it were code.
This case is the more actionable of the two: it means writing documentation that merely *mentions* a delete cmdlet can be blocked, even when the command performs no deletion at all.
### Expected behaviour
- The path reported in the deny message should be the path the delete cmdlet actually targets.
- A command with no delete operation should not be blocked by a delete-path rule.
- String literals / here-string contents should not be scanned as command tokens; or if the scanner cannot distinguish them, it should report "cannot statically verify — needs manual approval" rather than asserting a specific protected-path claim.
### Root cause
The fallback scanner splits the script on `[;|\n\r{}()&]+` (note: parentheses are split points), then per token:
- sets a "candidate protected path" variable on the **first** token that passes the protected-path predicate, guarded by an is-undefined check so it is **never overwritten**;
- sets a boolean when any token normalizes to a delete cmdlet (aliases are normalized correctly — `rm`, `del`, `rd`, `rmdir`, `ri`, `erase` all map through);
- after both loops, if the boolean is set and the candidate path is defined, it denies using that candidate — even though the two were discovered in unrelated statements.
The protected-path predicate returns true for a bare `/`: after backslash-to-slash normalization, `path.win32.isAbsolute('/')` is true and the normalized value equals `/`.
*Caveat on this section:* this was derived by extracting strings from the shipped bundle, so identifier names are not meaningful and may change between releases. The **mechanism** is the claim being made, and it is independently corroborated by Case B, which is reproducible from observable behaviour alone without any inspection of the bundle.
### Suggested fix
Scope the candidate-path search to the same statement in which the delete cmdlet was matched, instead of using a script-global variable. If no path token is found within that statement, return "cannot statically verify — requires manual approval" rather than borrowing a token from elsewhere.
This preserves the protection: a genuine `Remove-Item C:\Windows` still has its path token in the same statement and is still caught. It only removes the cross-statement false positives.
Additionally, string literals and here-string bodies should be excluded from token scanning in the fallback path.
### Notes
- `[System.IO.File]::Delete(...)` is not covered by this check at all (member invocation rather than a command); that is how the original operation was eventually completed. Raising this as context rather than as a security report: the check reads as a guardrail against accidental high-risk cmdlet use, not a security boundary. It may be worth being explicit about that in the docs so users do not over-rely on it.
- Environment: Windows 11, Claude Code CLI installed globally via npm (`%APPDATA%\npm\node_modules\@anthropic-ai\claude-code`), PowerShell 7.
### Workarounds (for others hitting this)
1. Keep delete operations as their own single-line invocation, without nested parentheses, separate from read/write/verify logic.
2. When writing text that mentions delete cmdlet names, write it with a file-editing tool rather than passing it through a PowerShell here-string.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by reproducing the two PowerShell cases described in the issue: the unrelated protected path and the here-string text trigger. Locate the fallback scanner used for multi-line or compound scripts and trace how statement tokens, delete commands, and string contents are combined. Done means genuine delete targets remain protected while unrelated paths and non-delete here-string content no longer cause a deny.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- powershell
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 50/100