Repository navigation
Windows native sandbox blocks all exec_command: 'permission roots or deny lists changed' #881
Description
Activity
Confirmed, and your root-cause trace is correct in every step. This is a real bug on
mainand the fix is already written, sitting in #808 waiting on review.I verified both halves rather than going from memory:
- The error you quote is
internal/sandbox/windows_setup.go:259onmaintoday. windowsSandboxProfileWithRuntimedoes not exist onmainat all.- On feat(sandbox): Windows sandbox principals (foundation for #662, does not close it) #808's branch it does, and it is applied on BOTH sides of the protocol:
windows_setup.go:210for the marker that setup writes, andwindows_runner.go:359for the profile the command presents.
That is exactly the asymmetry you identified. Setup fingerprinted the bare profile while every command arrived with the runtime root already appended by
permissionProfileWithRuntime, so a marker written seconds earlier was rejected and no command could run. It hit me the same way while building the identity work, which is why the fix lives in that PR rather than a standalone one.Two details from fixing it that your analysis did not have, and that matter if you are testing:
The augmentation has to add every runtime candidate, not the one the current process would select.
sandboxRuntimeRootForprefers the cache-derived root and falls back to a temp-derived one, and that choice is made per process, so setup granting only its own pick left a command that fell back writing to a tree with no ACE on it. Setup now covers both.The runner also cannot recompute the root itself. It runs re-exec'd as
zero __windows-command-runnerwithTMP/TEMPalready redirected into the sandbox runtime tree, soos.TempDir()there is not the operator's temp and the derivation comes out different. The profile is augmented in the parent and passed down.Workaround until #808 lands, with the trade-off stated plainly: turning the sandbox off in your user config with
"sandbox": {"enabled": false}restoresexec_command, at the cost of the confinement you set it up for. On an elevated VM you may be fine with that; on a real workstation I would not.Thanks for the report. The trace through
BuildCommandPlantoValidateWindowsSandboxSetupMarkeris more work than most issues get, and it is why this took two minutes to confirm rather than an afternoon.- The error you quote is
@baoyu0 update: the fix is now #901, a standalone PR against
main, rather than waiting on #808.When I replied earlier I pointed you at #808. Accurate, but not a good answer for you: #808 is a 12,000 line Windows principal-identity change with open architecture questions on it, so you would have been waiting on a design decision to get your shell back. #901 is 560 lines and does one thing.
Your suggested fix in the issue is what it implements, near enough word for word: prepare the runtime and apply the augmented profile before
BuildWindowsACLPlanandWriteWindowsSandboxSetupMarkerin the setup path. Two details on top of that, both of which broke it once while I was building:The augmentation has to grant every runtime candidate, not the one the current process would pick.
sandboxRuntimeRootForprefers the cache-derived root and falls back to the temp-derived one when the cache sits inside the workspace, and that choice is per process, so granting only setup's own pick left a command that fell back writing to a tree with no ACE on it.It also is not a pure lift onto
main, which matters if you build it yourself. The fix depends onfallbackSandboxRuntimeRootbeing deterministic, and onmainthat function is stillos.MkdirTempmemoized in a process-global map: setup grants temp root A, the next command derives root B, teardown cleans a third. So the derivation change came across with it. It is now a hash of the workspace and creates nothing, so every process reaches the same answer without sharing state.The workaround stands until this lands.
"sandbox": {"enabled": false}in your user config restoresexec_command, at the cost of the confinement you turned it on for. On the virtualized VM you described that is likely an acceptable trade for a few days; I would not run that way on a workstation.If you can build the branch and confirm it clears on your machine, that is worth more than my Windows 11 box agreeing with itself, and I will say so on the PR. No obligation, and it lands either way.
- added a commit that references this issue
on Aug 13, 2026 @Vasanthdev2004 verified #901 on my Windows 10 machine:
go build ./...— cleango test ./internal/sandbox/with the fix(sandbox): agree on the runtime root across the Windows setup marker #901-specific filters (Windows|RuntimeRoot|Marker) — all pass, including the runner-marker test that fails if the fix is reverted- Full
internal/sandboxsuite: 4 unrelated failures (TestBuildCommandPlanRejectsOutsideDirectory,TestResolveCommandDirAllowsExtraRootCwd,TestScopeAddNormalizesSymlinkedRoot,TestPermissionProfileIncludesReadOnlyGrantAsReadRootOnly) — I confirmed these fail identically on plainmain, so they're a local-environment artifact (repo cloned under%TEMP%breaks workspace-path expectations), not a regression from this PR.
Can confirm the fix clears on a real Windows box. Thanks for splitting #901 out of #808 — that was the right call.
- added 4 commits that reference this issue
on Aug 21, 2026 - addedissue-approvedReviewed and approved by the core team; community PRs may implement this issue.Reviewed and approved by the core team; community PRs may implement this issue.
on Aug 25, 2026 - added 4 commits that reference this issue
on Aug 27, 2026
Version / branch / commit
zero 0.6.0(npm@gitlawb/zero@0.6.0, win32-x64 binary)main@7f39a63OS and environment
windows-restricted-token(zero sandbox setupcompleted successfully from an elevated terminal)Steps to reproduce
Install
@gitlawb/zero@0.6.0and runzero sandbox setupfrom an elevated terminal. It printsWindows sandbox setup complete.Run
zero doctorfrom the same directory —sandbox.backendis[pass](the setup marker validates for the cwd workspace root).In any small project directory, run:
The agent's
exec_commandtool call aborts before the command executes.Expected behavior
exec_commandshould run inside the native Windows restricted-token sandbox (aszero doctorsuggests the backend is ready), just aswrite_file/read_file/edit_filework.Actual behavior
Every
exec_commandaborts immediately with:The run ends with
status: incomplete. File tools work fine — only shell execution is blocked.Root cause (traced on main @ 7f39a63)
internal/sandbox/runner.go—BuildCommandPlanbuilds the profile and then callspermissionProfileWithRuntime(profile, runtimeState)(line ~189).internal/sandbox/runtime_state.go—permissionProfileWithRuntimeappends the per-workspace sandbox runtime root (<LocalAppData>/zero/runtime/v1/<sha256(workspaceRoot)[:8]>) as an extraFileSystem.WriteRootsentry when it isn't already present.internal/sandbox/windows_command_runner_windows.go(restricted-token tier) validates the stored setup marker using the command's profile:ValidateWindowsSandboxSetupMarker(WindowsSandboxSetupConfigFromCommand(config)).zero sandbox setup(internal/cli/sandbox.go→runSandboxSetup) writes the marker viaWriteWindowsSandboxSetupMarkerfrom a profile built without the runtime root.internal/sandbox/windows_setup.go—ValidateWindowsSandboxSetupMarkercomparesACLPlanHash/ACLPlanEntriesof the stored marker (no runtime root) against the freshly computed plan from the command profile (with runtime root) → permanent mismatch →permission roots or deny lists changed.Evidence
C:\Users\<user>\AppData\Local\zero\runtime\v1\55c03089a6535cfb, where55c03089= first 8 hex chars ofsha256("<workspace>\zero-demo").aclPlanEntriesis stable for a given workspace (e.g., 5 for a workspace-only profile), while the command-time plan adds the runtime-root entries → counts/hashes never agree.zero doctorreports the backend as[pass]because doctor builds its profile the same way as setup (without the runtime root), so the two sides of the validation disagree.Suggested fix direction
Make the elevated Windows setup include the sandbox runtime root in the profile used both for applying ACLs and for writing the setup marker — i.e. prepare the runtime and apply
permissionProfileWithRuntimebeforeBuildWindowsACLPlan/WriteWindowsSandboxSetupMarkerin the setup path (runWindowsSandboxSetup/runSandboxSetup), so the command-time profile matches the marker.Relevant logs
(
zero doctorfrom the same directory:Overall: pass,sandbox.backend: [pass].)