mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-10 13:11:31 -10:00
* fix(sandbox): degrade Windows to permission-gating when not set up (stop bricking) On Windows the sandbox command runner hard-fails EVERY command until a one-time elevated `zero sandbox setup` writes the setup marker — so a fresh Windows user can't run a single shell command (the "windows sandbox is not initialized" error). Linux/macOS already degrade when their sandbox is unavailable; Windows was the lone outlier that bricks itself. BuildExecutionRequest now, on Windows when the setup marker is missing: - default (auto): DEGRADE to EnforcementDegraded — the command runs unwrapped under the in-process policy gate (workspace path-confinement) + per-command approval, with a one-time downgrade notice pointing at `zero sandbox setup` for full WFP/ACL/network isolation. - `--sandbox require`: still hard-errors with a clear setup hint, so strict callers keep their guarantee. - marker present: unchanged (native, wrapped, fully sandboxed). Matches every reference agent (codex defaults its Windows sandbox off and runs under approval; opencode/openclaude/vix degrade on Windows) — none brick the CLI because a setup step hasn't run. Test covers all three paths; sandbox/doctor/cli suites green; vet/build clean. * fix(sandbox): don't auto-allow shell commands while Windows is degraded Follow-up to the degrade fix in this PR. The permission engine's shellSandboxActive keyed on backend *capabilities* (CommandWrapping && NativeIsolation), which stay true on Windows even when setup hasn't run. So once the degrade fix let commands RUN unwrapped, the engine still auto-allowed ordinary shell commands as if sandboxed — running them with no OS isolation AND no permission prompt, breaking the per-command-approval floor the degrade is supposed to keep (the function's own doc comment promises "ordinary shell auto-allow never runs an unsandboxed command"). shellSandboxActive now returns false on Windows when the sandbox isn't initialized (mirrors manager.BuildExecutionRequest), so a degraded shell command falls through to the normal approval prompt. Network/destructive/out-of-workspace checks already ran earlier and are unaffected; native (post-setup) still auto-allows. Regression test covers degraded->prompt and native->auto-allow. Sandbox suite green.
49 lines
1.9 KiB
Go
49 lines
1.9 KiB
Go
package sandbox
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
// The engine's shell auto-allow must track the ACTUAL runtime enforcement, not
|
|
// just backend capability: on Windows the command is only wrapped once setup has
|
|
// run. Degraded (no marker) -> an ordinary shell command must PROMPT (per-command
|
|
// approval is the floor); native (marker present) -> auto-allow (the OS sandbox
|
|
// is the boundary). Regression guard for the #295 degrade gap.
|
|
func TestEngineWindowsShellAutoAllowTracksSetup(t *testing.T) {
|
|
restore := windowsSandboxInitialized
|
|
t.Cleanup(func() { windowsSandboxInitialized = restore })
|
|
|
|
eng := NewEngine(EngineOptions{
|
|
WorkspaceRoot: `C:\ws`,
|
|
Policy: DefaultPolicy(),
|
|
Backend: Backend{
|
|
Name: BackendWindowsRestrictedToken, Available: true, Executable: "zero.exe",
|
|
Platform: "windows", CommandWrapping: true, NativeIsolation: true,
|
|
},
|
|
})
|
|
req := Request{
|
|
ToolName: "bash", SideEffect: SideEffectShell,
|
|
Permission: PermissionPrompt, PermissionMode: PermissionModeAsk,
|
|
Args: map[string]any{"command": "echo hi"},
|
|
}
|
|
|
|
// Degraded: setup not run -> not actually sandboxed -> must prompt.
|
|
windowsSandboxInitialized = func() bool { return false }
|
|
if eng.shellSandboxActive(DefaultPolicy()) {
|
|
t.Fatal("degraded: shellSandboxActive must be false (command runs unwrapped)")
|
|
}
|
|
if d := eng.Evaluate(context.Background(), req); d.Action != ActionPrompt {
|
|
t.Fatalf("degraded ordinary shell command must PROMPT, got action=%q reason=%q", d.Action, d.Reason)
|
|
}
|
|
|
|
// Native: setup done -> the sandbox is the boundary -> auto-allow.
|
|
windowsSandboxInitialized = func() bool { return true }
|
|
if !eng.shellSandboxActive(DefaultPolicy()) {
|
|
t.Fatal("native: shellSandboxActive must be true")
|
|
}
|
|
if d := eng.Evaluate(context.Background(), req); d.Action != ActionAllow || !d.AutoAllowed {
|
|
t.Fatalf("native ordinary shell command must AUTO-ALLOW, got action=%q autoAllowed=%v", d.Action, d.AutoAllowed)
|
|
}
|
|
}
|