Files
zero/internal/sandbox/windows_setup_windows.go
Vasanth T 1b1abd5bf1 feat(sandbox): online/offline Windows network identity — run approved network commands sandboxed (#297)
* feat(sandbox): online/offline Windows network identity (run approved network commands)

Approved network commands bricked on Windows with "windows sandbox setup is out of
date: network policy changed": the setup marker was locked to network=deny, so any
command granted network (curl, git push, npm install) ran with network=allow and
failed marker validation.

Fix with a two-identity model that fits Zero's restricted-token architecture — no
extra logon users, no DPAPI, no admin at runtime: mint one synthetic
"offline-marker" SID per sandbox home and scope the persistent WFP block filter to
THAT SID only. Network is then gated purely by the restricted token's SID set:
- deny (offline): token = write-capability SIDs + offline-marker SID -> block filter
  matches -> no network; writes jailed by the capability SIDs.
- allow (online, approved): token = write-capability SIDs only -> filter never
  matches -> network reaches out; writes jailed identically.

The setup marker becomes mode-AGNOSTIC: it fingerprints the provisioned offline
infrastructure (NetworkInfraHash + OfflineFilterSID), not the per-command mode, so
one setup validly serves both an allow and a deny command. Schema bumped 3->4
(forces a clean re-setup; old markers scoped the filter to write SIDs).

- windows_runner.go: Offline SID in WindowsCapabilitySIDs (schema 2, back-compat
  upgrade) + WindowsOfflineMarkerSID + windowsRuntimeTokenSIDs.
- windows_network.go: BuildWindowsNetworkInfraPlan + WindowsNetworkInfraHash; drop
  the now-dead mode-coupled BuildWindowsNetworkPlan/*Hash helpers.
- windows_setup.go: marker schema 4; validation drops per-command-mode equality.
- windows_setup_windows.go: setup always installs the offline-scoped infra filter.
- windows_command_runner_windows.go: compose the token SID set per network mode.

Cross-platform core fully tested (mode selector, mode-independent infra+hash,
back-compat SID upgrade, one-marker-validates-both-modes regression, schema-3
rejected); sandbox/doctor suites + windows cross-compile green. The WFP
ALE_USER_ID/restricting-SID behavior needs the gated Windows integration smoke test.

* docs(sandbox): note the Schannel HTTPS limitation under the Windows restricted token

Document the known SEC_E_NO_CREDENTIALS limitation at the token-composition site:
an approved online command reaches the network, but HTTPS via Windows Schannel
fails inside the restricted token because Schannel can't acquire its per-user TLS
credential under a WRITE_RESTRICTED/LUA token. Fundamental restricted-token ↔
Schannel incompatibility, unsolved even in the reference sandboxes (codex#17459).
Workarounds: degraded mode or the in-process web_fetch tool. Comment-only.

* docs(sandbox): keep the Schannel-limitation note vendor-neutral
2026-06-21 22:49:54 +05:30

69 lines
2.4 KiB
Go

//go:build windows
package sandbox
import (
"fmt"
"io"
"golang.org/x/sys/windows"
)
func runWindowsSandboxSetup(config WindowsSandboxSetupConfig, stderr io.Writer) int {
// Applying the WFP network filters and workspace ACLs requires Administrator
// rights; without them WFP fails deep inside with a raw ACCESS_DENIED (0x5).
// Check up front and return an actionable message instead.
if !windowsProcessIsElevated() {
fmt.Fprintln(stderr, WindowsSandboxSetupName+": Administrator rights are required. Re-run `zero sandbox setup` from an elevated (Run as administrator) terminal.")
return 1
}
plan, err := BuildWindowsACLPlan(config.commandConfig())
if err != nil {
fmt.Fprintln(stderr, WindowsSandboxSetupName+": "+err.Error())
return 1
}
// Always provision the mode-INDEPENDENT infrastructure: the outbound block
// filters scoped to the offline-marker SID. Runtime gates network per command
// by whether the token carries that SID, so one setup serves both modes.
networkPlan, err := BuildWindowsNetworkInfraPlan(config.commandConfig())
if err != nil {
fmt.Fprintln(stderr, WindowsSandboxSetupName+": "+err.Error())
return 1
}
rollback, err := applyWindowsACLPlan(plan)
if err != nil {
fmt.Fprintln(stderr, WindowsSandboxSetupName+": "+err.Error())
return 1
}
if err := applyWindowsNetworkPlan(networkPlan); err != nil {
if rollbackErr := rollback(); rollbackErr != nil {
fmt.Fprintf(stderr, "%s: %v; rollback failed: %v\n", WindowsSandboxSetupName, err, rollbackErr)
return 1
}
fmt.Fprintln(stderr, WindowsSandboxSetupName+": "+err.Error())
return 1
}
if _, err := WriteWindowsSandboxSetupMarker(config); err != nil {
if rollbackErr := rollback(); rollbackErr != nil {
fmt.Fprintf(stderr, "%s: %v; rollback failed: %v\n", WindowsSandboxSetupName, err, rollbackErr)
return 1
}
fmt.Fprintln(stderr, WindowsSandboxSetupName+": "+err.Error())
return 1
}
return 0
}
// windowsProcessIsElevated reports whether the current process runs with an
// elevated (Administrator) token. On any error obtaining the token it returns
// true so the setup proceeds and surfaces the real WFP/ACL error rather than a
// false "needs admin" claim.
func windowsProcessIsElevated() bool {
var token windows.Token
if err := windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_QUERY, &token); err != nil {
return true
}
defer token.Close()
return token.IsElevated()
}