1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//! OS-level sandboxing for subprocess handlers.
//!
//! The [`Sandbox`] trait is the facade over per-OS enforcement
//! mechanisms (Linux seccomp+landlock, macOS sandbox-exec, Windows
//! Job Objects + restricted tokens). Concrete impls live in sibling
//! files behind `#[cfg(target_os = "...")]` and a feature flag per
//! platform; the [`NullSandbox`] no-op fallback ships on every
//! platform and is the default.
//!
//! ## Why a facade
//!
//! Each OS exposes a sharply different abstraction:
//!
//! - Linux: per-syscall filtering (seccomp) + per-path fs rules
//! (landlock). Fine-grained, programmatic.
//! - macOS: Scheme-based profile (`deny file-read* (subpath ...)`).
//! Coarse but expressive. Backed by `sandbox-exec`.
//! - Windows: Job Objects + restricted tokens + AppContainer +
//! Windows Filtering Platform. Per-resource-type, not
//! per-syscall.
//!
//! Our use case ("spawn this handler subprocess with declared-pure
//! capabilities") is uniform; the facade isolates the per-OS
//! asymmetry so the rest of `lex-extension-host` has one API.
//!
//! ## Lifecycle
//!
//! 1. The host constructs a [`Sandbox`] impl appropriate for the
//! running OS (or [`NullSandbox`] when no implementation exists).
//! 2. The host passes the impl into
//! [`SubprocessHandler::spawn_with_sandbox`] (or installs it on
//! a [`crate::TrustGate`] via [`crate::TrustGate::set_sandbox`]
//! for the auto-trust decision); the worker thread moves it in
//! by value.
//! 3. Before spawning the child process, the worker calls
//! [`Sandbox::apply_to`] with the declared
//! [`lex_extension::schema::Capabilities`]. The impl modifies the
//! [`std::process::Command`] in place (env vars, pre-exec hooks
//! on Unix, restricted-token hand-off on Windows) so the kernel
//! enforces the declared restrictions on the child.
//! 4. The trust gate consults [`Sandbox::available`] to decide
//! whether a pure handler can auto-trust (post-δ matrix flip in
//! PR 12d). Today's β/γ behavior — every subprocess prompts — is
//! preserved as long as [`NullSandbox::available`] returns
//! `false`.
//!
//! [`SubprocessHandler::spawn_with_sandbox`]: crate::transport::SubprocessHandler::spawn_with_sandbox
use Error;
use fmt;
use Arc;
use Capabilities;
pub use NullSandbox;
pub use LinuxSandbox;
pub use MacosSandbox;
/// Construct the OS-appropriate default [`Sandbox`] for the running
/// platform. The δ-phase ([lex#528]) trust-matrix flip uses this to
/// switch the engine's default from [`NullSandbox`] (post-plumbing
/// stand-in, never enforces) to the per-OS impl on supported
/// platforms.
///
/// Asymmetry by design:
///
/// - **Linux**: returns [`LinuxSandbox`]. `supports(pure)` returns
/// `true`, so the trust gate auto-trusts declared-pure handlers
/// under this default.
/// - **macOS**: returns [`MacosSandbox`]. `supports()` returns
/// `false` for every capability shape until a hardened
/// `(deny default)` SBPL profile lands, so the trust gate
/// continues to route pure handlers to the prompt path —
/// identical UX to Windows. `apply_to` still installs the limited
/// profile so handlers that do run after a user prompt still get
/// the partial denies.
/// - **Other (Windows etc.)**: returns [`NullSandbox`]. No
/// enforcement, no auto-trust — the trust gate prompts on every
/// subprocess handler, matching β/γ behaviour for now.
///
/// Returned as `Arc<dyn Sandbox>` so the host can install one
/// instance and share it with both [`crate::TrustGate::set_sandbox`]
/// and [`crate::transport::SubprocessHandler::spawn_with_sandbox`].
/// That sharing is load-bearing: the auto-trust decision must be
/// anchored on the *same* sandbox that actually enforces policy at
/// spawn time.
///
/// [lex#528]: https://github.com/lex-fmt/lex/issues/528
/// OS-level sandbox enforcement for subprocess handlers.
///
/// Implementations modify a `std::process::Command` so that the child
/// process can only perform the operations the [`Capabilities`]
/// argument permits. See the [module docs](self) for the lifecycle
/// and platform-specific notes.
/// Errors a [`Sandbox`] implementation can surface when installing a
/// policy at spawn time. Reserved for genuine kernel failures —
/// platform-capability mismatches are communicated through
/// [`Sandbox::supports`] *before* the trust gate decides, not here.