返回 DeepSeek-Reasonix
sandbox.go
根目录 / internal / sandbox / sandbox.go
1 // Package sandbox wraps a shell command in an OS-level jail so the model's
2 // `bash` calls are confined: it may read almost freely but write only inside
3 // the writable roots (workspace, configured extras, plus temp and toolchain
4 // caches), with optional forbid-read roots, and reach the network only when
5 // allowed. This is the *enforcement* layer beneath the permission rules
6 // (*policy*): a permitted command still cannot escape the box.
7 //
8 // macOS uses Seatbelt via sandbox-exec and Linux uses bubblewrap when available.
9 // Windows does not currently provide an OS-level bash sandbox and resolves the
10 // product setting to off. When enforce is requested but no OS sandbox backend
11 // is available, the bash tool fails closed instead of running the command
12 // unwrapped.
13 // Confining the in-process file-writer built-ins is handled separately, in
14 // package tool/builtin.
15 package sandbox
16
17 import (
18 "runtime"
19 )
20
21 // Spec describes how to confine one command. The zero value (Mode == "") does
22 // not enforce, so an unconfigured caller runs commands unchanged.
23 type Spec struct {
24 // Mode is "enforce" to wrap the command, anything else (incl. "off" and "")
25 // to run it unwrapped.
26 Mode string
27 // WriteRoots are directories the command may write to (the workspace root
28 // plus any configured extras). Platforms may add command-scoped temp/cache
29 // roots so builds and package managers keep working without broad writes.
30 WriteRoots []string
31 // ReadRoots are explicit host paths a Windows AppContainer may read. The
32 // macOS/Linux profiles already mount the host read-only by default.
33 ReadRoots []string
34 // AppContainerWriteRoots are the small subset of WriteRoots that a
35 // read-only Windows AppContainer may write (for MCP this is only its
36 // private state/temp tree). macOS and Linux already enforce this through
37 // WriteRoots and ignore this platform-specific distinction.
38 AppContainerWriteRoots []string
39 // DirectWrites marks a raw-argv launch as a write-capable command. On
40 // Windows this selects the low-integrity writer lane; it is deliberately
41 // false for ordinary read-only helpers such as rg.
42 DirectWrites bool
43 // ForbidReadRoots are files or directories the command may not read from
44 // when confined. The OS sandbox denies access to these paths (macOS Seatbelt
45 // deny file-read* rules, Linux bubblewrap masks); on other platforms the
46 // in-process tools enforce this instead.
47 ForbidReadRoots []string
48 // Network allows network egress from inside the sandbox. Off blocks it so a
49 // command cannot exfiltrate or fetch; many dev commands (module/package
50 // downloads) need it, so it defaults on at the config layer.
51 Network bool
52 // MinimalWrites omits the broad build-tool cache write allowances used by
53 // the bash sandbox. MCP profiles set it and explicitly provide only their
54 // private state/temp directories (plus approved writer roots).
55 MinimalWrites bool
56 // Shell is the interpreter the bash tool runs under. A zero value (empty
57 // Path) means the tool resolves one itself; the composition root sets it from
58 // [tools.shell] so the configured choice rides along with the spec.
59 Shell Shell
60 // SessionTemp is the absolute path of the logical-session private temporary
61 // directory for this command. When set, Linux bubblewrap binds it at /tmp
62 // (instead of a fresh tmpfs), and all platforms export TMPDIR/TMP/TEMP so
63 // consecutive Bash calls in the same session share temporary files. Empty
64 // keeps the platform default (ephemeral tmpfs on Linux bwrap, host temp
65 // elsewhere). MCP and other independent sandboxes leave this empty.
66 SessionTemp string
67 }
68
69 // Enforce reports whether the spec asks for confinement.
70 func (s Spec) Enforce() bool { return s.Mode == "enforce" }
71
72 // UnavailableMessage explains why an enforced bash sandbox cannot run and gives
73 // the platform-specific remediation.
74 func UnavailableMessage() string {
75 return "bash sandbox requested but unavailable on this host; refusing to run unconfined. " + UnavailableRemediation()
76 }
77
78 // UnavailableRemediation is split out so status surfaces can append the same
79 // actionable hint without repeating the leading error.
80 func UnavailableRemediation() string {
81 switch runtime.GOOS {
82 case "linux":
83 return "Install bubblewrap (`bwrap`) or set [sandbox] bash = \"off\" in config.toml / Settings -> Sandbox to restore pre-1.16 unconfined shell execution."
84 case "darwin":
85 return "Ensure `sandbox-exec` is available on PATH or set [sandbox] bash = \"off\" in config.toml / Settings -> Sandbox to restore pre-1.16 unconfined shell execution."
86 case "windows":
87 return "Windows does not currently provide a Reasonix OS-level Bash sandbox; the effective setting is fixed to \"off\" and shell commands run unconfined."
88 default:
89 return "Set [sandbox] bash = \"off\" in config.toml / Settings -> Sandbox to run shell commands unconfined on this platform."
90 }
91 }
92
93 // BackendUnavailableReason is safe diagnostic copy for subsystems such as MCP
94 // that intentionally continue unconfined when the OS backend is missing.
95 func BackendUnavailableReason() string {
96 switch runtime.GOOS {
97 case "linux":
98 return "bubblewrap (bwrap) is unavailable on PATH"
99 case "darwin":
100 return "sandbox-exec is unavailable on PATH"
101 case "windows":
102 return "the AppContainer helper or required Windows sandbox APIs are unavailable"
103 default:
104 return "this platform has no supported Reasonix sandbox backend"
105 }
106 }
107
107 lines GO