You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On Linux, safe mode has two independent layers: Landlock confines the filesystem at the kernel level, and the command scans (DESTRUCTIVE_RE, NETWORK_RE, and friends) are defense-in-depth on top of it.
On Windows there is no such layer. A search of the codebase finds no job object, restricted token, or AppContainer usage — the text scans are the only thing standing between a command and the host.
That's structurally unsound, because static analysis of a shell command is not decidable. #37 is a concrete instance: PowerShell resolves commands at runtime, so $c='Invoke-WebRequest'; & $c example.com names no cmdlet and passes every scan. We fixed that by gating dynamic syntax behind a permission, which restores the "text is literal" precondition the scans need — but that is a narrower boundary, not a real one, and the same class of problem will keep appearing for any shell we support.
What the ecosystem does
Both major agents treat authorization and isolation as separate axes, and put the security boundary in the OS:
Codex ships a mandatory OS-native sandbox on Linux, macOS, and Windows.
Claude Code uses bubblewrap+seccomp on Linux, Seatbelt on macOS, and Job Objects on Windows. Its static command parser exists only to decide whether to skip an approval prompt when the sandbox is already on — when the parser can't analyze something it prompts, and the sandbox still contains the command either way.
In other words, nobody relies on command-text matching as the boundary. Blacklists survive only as a thin always-on check for catastrophic commands (rm -rf /).
Proposal
Add OS-level confinement for exec_command on Windows — job object plus a restricted token, or AppContainer — with the same filesystem read/write roots the Landlock path already computes (landlock_write_roots(), guard_allow_roots()).
Once that exists:
The command scans can be demoted to defense-in-depth on Windows, matching Linux.
The shell choice (pwsh vs cmd) stops being a security decision and becomes an ergonomics one — pwsh would still be preferred for UTF-8 output determinism, but a documented cmd fallback would no longer be dangerous.
Problem
On Linux,
safemode has two independent layers: Landlock confines the filesystem at the kernel level, and the command scans (DESTRUCTIVE_RE,NETWORK_RE, and friends) are defense-in-depth on top of it.On Windows there is no such layer. A search of the codebase finds no job object, restricted token, or AppContainer usage — the text scans are the only thing standing between a command and the host.
That's structurally unsound, because static analysis of a shell command is not decidable. #37 is a concrete instance: PowerShell resolves commands at runtime, so
$c='Invoke-WebRequest'; & $c example.comnames no cmdlet and passes every scan. We fixed that by gating dynamic syntax behind a permission, which restores the "text is literal" precondition the scans need — but that is a narrower boundary, not a real one, and the same class of problem will keep appearing for any shell we support.What the ecosystem does
Both major agents treat authorization and isolation as separate axes, and put the security boundary in the OS:
In other words, nobody relies on command-text matching as the boundary. Blacklists survive only as a thin always-on check for catastrophic commands (
rm -rf /).Proposal
Add OS-level confinement for
exec_commandon Windows — job object plus a restricted token, or AppContainer — with the same filesystem read/write roots the Landlock path already computes (landlock_write_roots(),guard_allow_roots()).Once that exists:
safe-mode PowerShell dynamic-syntax gate added in Windows: execute string commands with PowerShell 7 #37 could be relaxed, because a command the parser can't analyze would still be contained.Related: #36 (Windows runtime work), #37.
Interim state
Until this lands,
docs/limitations.mdstates plainly that Windowssafemode is a best-effort gate rather than a boundary.