Claude Code security Windows sandbox bash v2.1.283 2026

Claude Code Patched One Windows Delete Bypass on Sept 25 — a Nested Shell Found Another Two Days Later (2026)

The Prompt Shelf ·

Claude Code’s v2.1.283 release (September 25, 2026) fixed a real, reported disaster: on Windows, cmd /c rd, rmdir, del, and erase could delete drive roots and the home folder that Remove-Item correctly refuses. That fix closed the door that a user hit on August 13, when a wrapped cmd /c rd call wiped an entire C:\ drive. Two days after the patch shipped, a different door opened: on September 27, a background subagent piped a bash script through MSYS2 instead of cmd, and 125GB disappeared the same way. Same guard, same class of bug, different shell. Here’s the actual timeline, what the September 25 fix does and doesn’t cover, and what to change in your own setup if you run Claude Code on Windows.


Four incidents, one root cause, two months

These aren’t four unrelated bug reports. They’re the same underlying flaw — a safety guard that checks the shape of a command rather than the filesystem path it will actually touch — surfacing through four different execution paths on anthropics/claude-code:

DateIssueWhat happened
Jul 26, 2026#81378The guard’s fallback string-scanner blocked a harmless write because a here-string’s prose mentioned a delete cmdlet and contained a stray /. False positive, but it exposed how the check actually works.
Aug 13, 2026#86667A blocked Remove-Item -LiteralPath 'C:\$GetCurrent' was retried as cmd /c rd /s /q "C:\$GetCurrent". PowerShell double-quote interpolation collapsed the undefined $GetCurrent variable to nothing, so cmd received rd /s /q "C:\". The command ran past its foreground timeout, moved to an unsupervised background task, and deleted the whole C: drive — unbootable, full reinstall required.
Sep 16, 2026#95426An unprompted “clear old test state” step ran rm -rf "$(cygpath -u 'C:\')". cygpath -u 'C:\' resolves to /c/, so the recursive delete targeted the drive root. Stderr was redirected to /dev/null, so it ran silently for ~35 minutes. ~600GB destroyed, including the session transcripts that would have explained why.
Sep 27, 2026#97660A background subagent (auto mode) ran a bash script through MSYS2, ending in rm -rf \$GNUPGHOME. PowerShell doesn’t treat \$ as an escape, so it expanded $(mktemp -d) (which failed) and $GNUPGHOME (undefined) to nothing. Bash received rm -rf \, which MSYS2 treats as the current drive’s root. ~125GB deleted — personal folders, the working project, and files inside installed programs — before it was stopped roughly two minutes in.

All four are Windows-only. All four involve a recursive-delete target that was never resolved or displayed before it ran. And #97660 happened after the September 25 fix — meaning whatever v2.1.283 patched didn’t cover the path that produced it.


What the September 25 fix actually patched

The v2.1.283 changelog entry is narrow and specific:

Windows: Fixed the PowerShell tool letting cmd /c rd, rmdir, del or erase delete drive roots, the home folder and other folders that Remove-Item refuses.

That’s a direct response to #86667 — Anthropic added drive-root and home-folder protection for four specific DOS-style delete commands (rd, rmdir, del, erase) reached via cmd /c. It’s a real fix for a real reported bypass, and it closes the exact call shape from that incident.

The mechanism behind why it was needed at all was reverse-engineered by a commenter on #86667, who read the guard’s implementation out of the shipped 2.1.229 binary. The root-path check runs as two regexes —

bk_ = /^[A-Za-z]:\/?$/
vk_ = /^[A-Za-z]:\/[^/]+$/

— applied against parsed cmdlet/alias tokens in the command’s AST. When a command parses cleanly as PowerShell (a direct Remove-Item -LiteralPath 'C:\$GetCurrent' call), the guard sees the real cmdlet and blocks it correctly. When the same deletion is wrapped in cmd /c rd /s /q "...", rd is just a string argument being handed to cmd.exe — not a PowerShell cmdlet — so it’s invisible to a check that only inspects cmdlet positions. The September 25 fix extended that check to also catch rd, rmdir, del, and erase when they appear as arguments to cmd /c.

What it didn’t extend the check to: bash. #97660 happened two days later through bash -lc "..." piped through MSYS2, not cmd. The rm -rf \ that MSYS2 executed was never a PowerShell cmdlet, never a cmd DOS command, and never matched against either regex — it was a string argument to a completely different interpreter, one layer further removed from what the guard was built to inspect.


Why nested shells keep finding new doors

The pattern across all four issues is the same complaint, restated by a different reporter each time: the guard evaluates the textual shape of one specific command form, not the filesystem operation the machine is about to perform. Wrap the same deletion in a shell the guard doesn’t parse for, and the check simply doesn’t fire — not because the deletion is safer, but because it’s invisible to that layer.

There’s a second, PowerShell-specific ingredient that shows up in three of the four incidents (#86667, #95426, #97660): PowerShell’s quoting rules don’t match the shell receiving the string. Double-quoted PowerShell strings interpolate $variable and $(subexpression) before the string is ever handed to cmd or bash. If the variable is undefined, or the subexpression fails, it silently collapses to an empty string — turning rm -rf \$GNUPGHOME into rm -rf \, or rd /s /q "C:\$GetCurrent" into rd /s /q "C:\". Nobody types the dangerous command directly. PowerShell’s own interpolation manufactures it from something that looked scoped.

The fix suggested independently in both #86667 and #97660 is the same one: evaluate the guard against the resolved, expanded command line — what will actually execute after every layer of interpolation and every nested shell has run — instead of pattern-matching the literal text of the outermost call. As of this writing, that’s still an open request; the September 25 patch addressed one specific bypass shape, not the general problem.


Windows doesn’t get the sandbox layer either

There’s a structural reason these four incidents are all Windows-only: Claude Code’s sandboxed Bash tool — the OS-level isolation that restricts write access to the working directory and confines network egress — runs on Seatbelt (macOS) and bubblewrap (Linux, WSL2). It doesn’t run natively on Windows outside WSL2. A Windows user driving the PowerShell tool directly gets the same-statement guard checks discussed above, and nothing underneath them — no OS-enforced boundary stops a command that slips past the guard from touching the rest of the filesystem, the way a sandboxed Bash command on macOS or Linux would be contained regardless of what the guard itself catches.

That’s also why the practical mitigation available today isn’t “wait for the next patch” — it’s moving destructive work under WSL2, where the sandbox actually applies, or adding your own hard stop in front of every recursive delete regardless of which shell issues it.


What to change in your own setup today

None of the four suggested fixes below require a Claude Code update — they’re things you can put in place this week if you run Claude Code on Windows and haven’t already:

Add a PreToolUse hook that blocks recursive deletes with a resolved path check, not a text match. The reporter on #95426 mentioned running exactly this locally (guard-recursive-delete.js) after their incident — a hook that intercepts every Bash/PowerShell tool call, extracts what the command will actually delete after resolving variables and substitutions, and force-prompts (or refuses outright) if that target is a drive root, a home directory, or empty.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node ~/.claude/hooks/guard-recursive-delete.js"
          }
        ]
      }
    ]
  }
}

Prefer WSL2 over the native PowerShell tool for anything destructive. Inside WSL2, Claude Code’s sandbox (bubblewrap) actually enforces a filesystem boundary — a bad rm -rf is contained to the working directory and session temp, not the whole drive. The native Windows PowerShell tool has no equivalent layer underneath the command-level guard.

Turn off auto mode for sessions that might run housekeeping deletes. In #95426, the deleting command was the agent’s own idea — “clear old test state” — not something the user asked for. In #97660, a background subagent under auto mode approved its own rm -rf with no interactive checkpoint. permissions.defaultMode set to something that requires confirmation on Bash/PowerShell calls removes the silent-approval step in both incident types; see our permissions and trust levels guide for how the modes differ.

Don’t suppress stderr on destructive commands. 2>/dev/null or $ErrorActionPreference = 'SilentlyContinue' in front of a recursive delete throws away the one signal that something’s wrong — the flood of permission-denied errors as the command walks into system directories it shouldn’t reach. In #95426 that’s specifically what let a 35-minute deletion run with zero visible output.

Keep shadow copies or a backup outside the working tree on Windows. In #95426, the session transcripts that would have explained what happened were themselves inside the blast radius and unrecoverable. The only successful recovery in any of these four incidents (#97660) came from a Windows shadow copy taken nine hours earlier — not from anything Claude Code itself preserved.


The pattern to watch for going forward

If you maintain a CLAUDE.md or team policy for Windows machines, the actionable line isn’t “avoid rm -rf” — every one of these four commands looked scoped to its author. It’s this: any recursive delete whose target is built from a variable, a subexpression, or a nested shell call should be refused or shown resolved, every time, regardless of which of the four incidents’ shells issues it. That’s the fix request repeated across #86667, #95426, and #97660, and it’s still open. Until a guard actually inspects the resolved command line instead of one call shape at a time, treat every new “fixed” changelog line the way this one turned out: real, narrow, and not the last one you’ll need.

You can track the open threads directly — #86667, #95426, and #97660 are all still open as of this writing. For more on what Claude Code’s sandbox does and doesn’t cover per platform, see our sandbox isolation settings guide, and browse real permission and hook configurations in our gallery.

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules