What the code assumes
Two places state that the sandbox is what makes auto-approval safe:
plugins/grok-build/scripts/grok-bridge.mjs:331-333, on the review and critique path:
// Headless runs have no user to click Approve, so "plan" mode with no
// approver can hang or fail on any tool call. `sandbox: "read-only"` is
// the actual safety boundary here, so auto-approving within it is safe.
plugins/grok-build/scripts/grok-bridge.mjs:450-453, on the run path, says the same for both modes.
Both are unqualified, and buildHeadlessArgs (lib/grok.mjs:155-201) passes no tool restrictions of any kind — no --disallowed-tools, no --deny, no --tools. So --sandbox read-only is not the primary barrier for a read-only run, it is the only one.
Why that does not hold on Windows
Your own sandbox documentation lists enforcement mechanisms for two platforms:
Linux | Landlock | Kernel 5.13 or later
macOS | Seatbelt | macOS (all versions)
Windows is not among them. And in xai-org/grok-build, crates/codegen/xai-grok-sandbox/src/lib.rs, apply() is gated:
#[cfg(all(feature = "enforce", unix))]
pub fn apply(&mut self, workspace: &Path) -> anyhow::Result<()> { /* kernel enforcement */ }
#[cfg(not(all(feature = "enforce", unix)))]
pub fn apply(&mut self, _workspace: &Path) -> anyhow::Result<()> {
tracing::info!(profile = %self.profile,
"Sandbox enforcement unavailable (built without 'enforce' feature)");
Ok(())
}
Off unix the call is a no-op. (Small aside, since it cost me time: on Windows that log line names the wrong cause — the gate is unix, not the enforce feature — and it is info! rather than warn!, so nothing reaches a headless user.)
Net effect on Windows: a read-only /grok-build:review, critique or run invokes the CLI with --always-approve and a --sandbox read-only flag that enforces nothing.
Measured
Calling grok directly with this plugin's argv shape — --sandbox read-only --always-approve, no tool restrictions — on Windows:
| Case |
With --sandbox read-only |
Control, no sandbox |
| Write a file outside the cwd |
succeeded |
succeeded |
| Read a file outside the cwd, returning a distinctive token |
succeeded, token returned |
succeeded |
Controls included because an unwritten file proves nothing on its own. No warning printed, exit codes normal, and ~/.grok/sandbox-events.jsonl gained no entries. Same result on grok 1.0.0 (3cd0d0cbce) and 0.2.118 (1e1687c1cf), so this is not a recent regression.
Two ways to close it, and I am happy to send either
1. Minimal — make the comments true. Qualify both passages with the platform limitation and cite the sandbox docs, so the next person reading them does not conclude auto-approval is confined when it is not. Optionally emit a warning on win32 at run time. This fixes the documentation defect and nothing else; the behaviour stays as it is.
2. Substantive — enforce at the tool layer instead. Pass --disallowed-tools for the writing tools and --deny rules alongside the sandbox, rather than relying on the sandbox alone. This holds on every platform because it asks nothing of the OS — the CLI simply is not given the tools, and the deny rules are evaluated by the CLI's own permission policy rather than by a kernel feature. The sandbox then becomes an extra layer where the platform supports it, rather than the load-bearing one.
Option 2 is what I do in a downstream fork, where it has held up in testing on Windows, Linux (aarch64) and macOS — the relevant part is a tool list and a set of deny rules assembled in buildHeadlessArgs. Apache-2.0, so take it, adapt it or ignore it.
One caveat if you do go that way, learned the hard way and probably the most useful thing in this issue: a hard-coded tool list decays. Mine listed four ids while the CLI's registry grew to roughly twenty, so write had quietly become reachable and only a deny rule was still stopping it. I found that by auditing against the installed binary, not by anything failing. Whatever list you choose wants a way to notice when it has gone stale — otherwise it degrades into exactly the kind of protection that looks present and is not, which is the same failure mode as the sandbox flag this issue is about.
What makes option 2 attractive is where this plugin sits: it constructs the argv, so it can compensate for a platform gap in the CLI without the CLI changing. That is a property of being the layer in between, and it is available here in a way it is not available to either side alone.
#23 is option 1 — comments only, no behaviour change, so it can be merged or closed without committing you to anything. Option 2 is a change to your safety model rather than a patch, so it seemed wrong to send unasked; say the word and I will put one together, or ignore it entirely. Your call on both.
What the code assumes
Two places state that the sandbox is what makes auto-approval safe:
plugins/grok-build/scripts/grok-bridge.mjs:331-333, on the review and critique path:plugins/grok-build/scripts/grok-bridge.mjs:450-453, on the run path, says the same for both modes.Both are unqualified, and
buildHeadlessArgs(lib/grok.mjs:155-201) passes no tool restrictions of any kind — no--disallowed-tools, no--deny, no--tools. So--sandbox read-onlyis not the primary barrier for a read-only run, it is the only one.Why that does not hold on Windows
Your own sandbox documentation lists enforcement mechanisms for two platforms:
Windows is not among them. And in
xai-org/grok-build,crates/codegen/xai-grok-sandbox/src/lib.rs,apply()is gated:Off unix the call is a no-op. (Small aside, since it cost me time: on Windows that log line names the wrong cause — the gate is
unix, not theenforcefeature — and it isinfo!rather thanwarn!, so nothing reaches a headless user.)Net effect on Windows: a read-only
/grok-build:review,critiqueorruninvokes the CLI with--always-approveand a--sandbox read-onlyflag that enforces nothing.Measured
Calling
grokdirectly with this plugin's argv shape —--sandbox read-only --always-approve, no tool restrictions — on Windows:--sandbox read-onlyControls included because an unwritten file proves nothing on its own. No warning printed, exit codes normal, and
~/.grok/sandbox-events.jsonlgained no entries. Same result ongrok 1.0.0 (3cd0d0cbce)and0.2.118 (1e1687c1cf), so this is not a recent regression.Two ways to close it, and I am happy to send either
1. Minimal — make the comments true. Qualify both passages with the platform limitation and cite the sandbox docs, so the next person reading them does not conclude auto-approval is confined when it is not. Optionally emit a warning on win32 at run time. This fixes the documentation defect and nothing else; the behaviour stays as it is.
2. Substantive — enforce at the tool layer instead. Pass
--disallowed-toolsfor the writing tools and--denyrules alongside the sandbox, rather than relying on the sandbox alone. This holds on every platform because it asks nothing of the OS — the CLI simply is not given the tools, and the deny rules are evaluated by the CLI's own permission policy rather than by a kernel feature. The sandbox then becomes an extra layer where the platform supports it, rather than the load-bearing one.Option 2 is what I do in a downstream fork, where it has held up in testing on Windows, Linux (aarch64) and macOS — the relevant part is a tool list and a set of deny rules assembled in
buildHeadlessArgs. Apache-2.0, so take it, adapt it or ignore it.One caveat if you do go that way, learned the hard way and probably the most useful thing in this issue: a hard-coded tool list decays. Mine listed four ids while the CLI's registry grew to roughly twenty, so
writehad quietly become reachable and only a deny rule was still stopping it. I found that by auditing against the installed binary, not by anything failing. Whatever list you choose wants a way to notice when it has gone stale — otherwise it degrades into exactly the kind of protection that looks present and is not, which is the same failure mode as the sandbox flag this issue is about.What makes option 2 attractive is where this plugin sits: it constructs the argv, so it can compensate for a platform gap in the CLI without the CLI changing. That is a property of being the layer in between, and it is available here in a way it is not available to either side alone.
#23 is option 1 — comments only, no behaviour change, so it can be merged or closed without committing you to anything. Option 2 is a change to your safety model rather than a patch, so it seemed wrong to send unasked; say the word and I will put one together, or ignore it entirely. Your call on both.