[BUG] rm -rf with unexpanded ${LOCALAPPDATA} in bypassPermissions deletes real system directory

Resolved 💬 5 comments Opened Mar 26, 2026 by az-dev-me Closed May 11, 2026

Description

Claude Code (v2.1.84, Opus 4.6, VS Code extension) executed rm -rf on what it thought was a local project folder named ${LOCALAPPDATA}/. Bash expanded the variable to the real Windows system directory AppData\Local, deleting 50+ application folders.

This is a supply-chain failure between two AI agent sessions — distinct from NTFS junction traversal (#36339, #29249) or variable folder creation alone (#13138).

Root Cause Chain

Stage 1 — C# bug creates the booby-trap (Session A):

Session A was building an MCP server framework. Its C# SecurityPolicyEngine had config defaults like "${LOCALAPPDATA}\PAC-X\logs". An ExpandPath() method existed but was only called when loading a config file. When no file existed (the default case), Directory.CreateDirectory() used the literal string, creating a real folder named ${LOCALAPPDATA}/ in the working directory.

Stage 2 — MCP server plants it in client workspace (cross-session):

Session B was building a plugin that USES Session A's MCP server as a client. The MCP server launches AutoCAD, which sets its working directory to Session B's project folder. The literal ${LOCALAPPDATA}/ folder was created there.

Stage 3 — Client AI triggers the trap (Session B):

git status in Session B showed ?? ${LOCALAPPDATA}/ as untracked. Session B's AI, cleaning up the project, included it in a batch delete:

rm -rf build_out/ pacx_debug/ tests/dwg/ "${LOCALAPPDATA}/"

Bash expanded "${LOCALAPPDATA}" to C:\Users\<user>\AppData\Local, deleting the entire directory.

Stage 4 — Server AI diagnoses and repairs (Session A):

Session A (still running, unaware of the destruction) investigated, found the C# bug in code it had written, fixed it, backed up surviving data, and assisted with recovery. Same model, same day.

Environment

  • OS: Windows 11 (10.0.26100)
  • Claude Code: v2.1.84 (VS Code extension in Cursor)
  • Model: claude-opus-4-6
  • Shell: Git Bash
  • Permission mode: bypassPermissions

Impact

  • 50+ folders deleted from AppData\Local
  • Applications installed in Local\Programs destroyed (Cursor, VS Code — both needed reinstall)
  • Browser local data, Docker data, dev tool caches all gone
  • Only ~17 folders survived (locked by running processes)
  • AppData\Roaming was NOT affected

The Safety Gap

Claude Code does not validate rm -rf targets after shell expansion. The AI treated ${LOCALAPPDATA}/ as a literal local folder name. Three failures:

  1. No expansion check: Arguments containing ${}, ~, or %VAR% are not resolved before execution to detect dangerous targets.
  2. No system path blocklist: Paths resolving to AppData, Users, Program Files, home directories, etc. are not blocked from rm -rf.
  3. bypassPermissions too permissive: Even in bypass mode, destructive operations on system directories should require confirmation.

Recommendations

  1. Pre-expansion validation: Before executing any rm/rmdir command, resolve shell variables and check if the resulting path is inside the workspace. If not, refuse with a clear error.
  1. System directory blocklist: Hard-block rm -rf on paths matching known system directories (AppData, Users, Program Files, Windows, System32, $HOME, ~/, etc.).
  1. Bypass mode scoping: bypassPermissions should mean "trust me with project files", not "trust me with my entire filesystem". System-level destructive ops should always confirm.
  1. git status warning: Entries containing ${}, %VAR%, or ~ likely represent unexpanded environment variables from buggy code — flag them as suspicious before cleanup.

Reproduction

  1. In a git repo on Windows with Git Bash:

``bash
mkdir '${LOCALAPPDATA}'
git status # shows: ?? ${LOCALAPPDATA}/
``

  1. Run Claude Code with bypassPermissions
  2. Ask it to "clean up untracked files" or "remove build artifacts"
  3. Observe that it may include "${LOCALAPPDATA}/" in rm -rf — bash expands it to the real system path

Why This Is Not a Duplicate

| Issue | Root cause | This issue |
|-------|-----------|------------|
| #36339 | NTFS junction traversal in pnpm | No junctions — shell variable expansion |
| #29249 | NTFS junction traversal in pnpm worktrees | No junctions — shell variable expansion |
| #13138 | Folders created with literal variable names | Same C# symptom, but #13138 doesn't address the Claude Code rm -rf safety gap |

The core request — pre-expansion validation of rm -rf targets — is not covered by any existing issue.

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗