Skip to content

Commit 86ceab7

Browse files
adpa-msCopilot
authored andcommitted
fix(isolation-session): refuse network policy the backend cannot enforce; require the unrestricted-network acknowledgment (#682)
* Refuse dishonest network policy on the IsolationSession backend The IsolationSession container runs on an unrestricted network (outbound open; a process inside can listen on a localhost-reachable port) that MXC cannot filter or deny. Previously the backend accepted the default `Block` network policy, silently affirming a deny it cannot enforce. Now provision (and one-shot, which runs the full lifecycle) accept ONLY the canonical unrestricted-network acknowledgment: network.defaultPolicy=allow + allowLocalNetwork=true, no allowed/blocked hosts, no proxy, default enforcement. Everything else (including an absent policy, which defaults to the unenforceable `Block`) is refused. Post-provision phases reject any supplied network policy (fixed at provision) via a new domain `ExecutionRequest.network_specified` flag set from wire `network` presence in config_parser; an absent policy is inherited. This closes the domain-model blind spot where an explicit default-valued `Block` is indistinguishable from absent. Reworded the network/proxy error messages and added a post-provision "immutable" message. Updated the happy-path iso test configs to the canonical form; post-provision and negative-path configs are intentionally unchanged. Gates: cargo fmt --check; clippy --all-features; cargo test iso ON and OFF (wxc_common + isolation_session_common green, 84 iso + 456 wxc_common); parser network_specified tests; wxc_host_prep 16 passed (elevated). Pre-existing env failures (microvm e2e staging) are identical iso ON/OFF and unrelated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 47662115-cf22-4564-8930-370dfbee63fb * SDK: require the unrestricted-network acknowledgment at iso provision Mirror the Rust-side IsolationSession network fix in the TypeScript SDK. `IsolationSessionProvisionConfig.network` is now a required field typed as the exact literal `{ defaultPolicy: 'allow'; allowLocalNetwork: true }`, so the caller must explicitly acknowledge that the isolation session container runs on an unrestricted network the backend cannot filter or deny. Any other value (or a wrong-typed one) is a compile error; the post-provision configs intentionally expose no `network` field, so the type system enforces the provision-only rule for SDK callers. Tests: type-level @ts-expect-error assertions (required network; block and allowLocalNetwork=false rejected; network rejected on post-provision configs); provisionSandbox lifts the canonical network to the envelope top level; updated the conformance oracle's LiftedPhaseKey and the integration test's provision calls. npm run build + npm test green (207 tests). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 47662115-cf22-4564-8930-370dfbee63fb * docs: IsolationSession network policy is honesty-gated, not rejected Update the in-repo docs to match the new behavior: the backend still rejects all filesystem policy (no host-folder-sharing primitive), but the network policy is now honesty-gated. Provision (and one-shot) require the canonical unrestricted-network acknowledgment (network.defaultPolicy=allow + allowLocalNetwork=true, no host rules, no proxy, default enforcement) and refuse anything else, including an absent policy; post-provision phases reject any supplied network policy and inherit an absent one. Touches: copilot-instructions iso backend row; docs/isolation-session/oneshot (policy-validation coverage row) + state-aware-rust (prose + policy matrix) + state-aware-typescript (provision config table gains the required `network` field); sdk/node/README state-aware example now passes the acknowledgment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 47662115-cf22-4564-8930-370dfbee63fb * test: expand IsolationSession network coverage + fix E2E probe Add end-to-end and integration tests for the new network-acknowledgment behavior, and fix a latent bug the required-network change introduced in the SDK E2E probe. Fix (critical): `probeStateAwareRuntime` (the module-load skip probe in the SDK integration suite) provisioned iso with no config. With `network` now required at provision, that returns `policy_validation`, which the probe rethrows — so on an iso-capable host the whole integration suite would error at load instead of running. The probe now passes the canonical network acknowledgment for iso. New integration tests (sdk/node integration, real wxc-exec): the backend refuses a provision that (a) omits the network acknowledgment, (b) sends a non-canonical network (defaultPolicy=block), or (c) omits allowLocalNetwork — each via an untyped call, proving the runtime guard holds even when a JS caller bypasses the compile-time type. New E2E tests (PowerShell VM suites): one-shot refuses block / allow-without- allowLocalNetwork / allowedHosts; state-aware refuses a non-canonical network at provision and refuses any network policy on the start and exec post-provision phases (even the canonical acknowledgment, since it is immutable post-provision). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 47662115-cf22-4564-8930-370dfbee63fb * fix: add canonical network ack to iso TTY resize smoke config The IsolationSession TTY resize smoke (run_isolation_session_resize_smoke.ps1) builds its wxc-exec config inline and was the one manual (-Manual-only) test not covered by the automated suite, so it was missed when the network-acknowledgment requirement landed. Without a network block the backend now rejects it with policy_validation. Add the canonical {defaultPolicy:allow, allowLocalNetwork:true} form (matching isolation_session_powershell_interactive.json) so the manual smoke provisions again. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 47662115-cf22-4564-8930-370dfbee63fb * docs: align state-aware design + GA networking docs with iso network policy The cross-backend state-aware design docs and the v2 GA networking scope doc still described IsolationSession as honoring filesystem (not network) at provision, and showed provision examples using network.allowedHosts -- all now rejected by the backend. Align them with shipped behavior and the SDK type: provision honors only the canonical unrestricted-network acknowledgment ({ defaultPolicy: 'allow', allowLocalNetwork: true }); filesystem policy is rejected and ui is ignored. - networking.md: reword the IsolationSession network-scope line (requires the canonical ack; rejects everything else). - mxc-state-aware-sandbox-api.md / -overview.md: fix the 10.3 honor matrix (iso filesystem applied->rejected, ui applied->ignored), the SDK-exposure prose, the IsolationSessionProvisionConfig type (required network literal; drop filesystem and ui), and the provision examples (drop filesystem, allowedHosts->allowLocalNetwork). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 47662115-cf22-4564-8930-370dfbee63fb * docs: address PR review -- stale validator comment, fragile test counts, policy.ui gap state_aware.rs: the block comment above the validate_<phase> hooks still said network policy was rejected at every phase. That stopped being true when provision started accepting the canonical unrestricted-network acknowledgment. Correct the network clause while keeping the statements that are still true (the backend has no network primitive; proxy policy is rejected at every phase). oneshot.md: drop the Test Plan Count column and the '~31 backend-specific' / '287 total currently passing' figures. They were already stale (policy.rs has 28 tests, listed as ~24) and the rows never summed to the stated total. No other backend doc or README tracks test counts, so removing them makes this doc consistent and deletes a number that silently rots whenever a test is added. The durable Category / Location / What-it-verifies content stays. state-aware-rust.md: policy.ui was documented as rejected at every phase, but the backend never validates it, so a supplied UI policy is silently ignored. Correct the matrix to 'ignored' (matching the runtime and the state-aware design doc) and add an explicit known-gap note -- this is the same false-guarantee shape the network honesty gate closes, so rejecting policy.ui is the intended end state, not a deliberate exemption. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 47662115-cf22-4564-8930-370dfbee63fb * refactor: move network_specified onto ContainerPolicy Addresses PR review feedback: the presence signal describes the content of the policy object, not the invocation, so it belongs beside the other parse-derived policy member (`#[serde(skip)] network_proxy`) rather than next to ExecutionRequest's invocation flags (experimental_enabled, testing_features_enabled, dry_run, audit). Behavior-preserving: the flag is still captured once in the parser (`convert_wire_config`), which is also the path `mxc_engine::build_request` round-trips through, so the Rust SDK / FFI / C# callers are unaffected. `#[serde(skip)]` keeps it off the wire, so the schema and generated SDK types are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 47662115-cf22-4564-8930-370dfbee63fb --------- Co-authored-by: adpa-ms <> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 47662115-cf22-4564-8930-370dfbee63fb
1 parent 41839e8 commit 86ceab7

39 files changed

Lines changed: 742 additions & 126 deletions

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ The Rust workspace (`src/`) implements multiple sandboxing backends behind the `
113113
| Windows Sandbox | `wxc-exec.exe` | Windows | `backends/windows_sandbox/lifecycle/src/` (live transient one-shot `WindowsSandboxRunner` + state-aware `StatefulSandboxBackend`). Experimental — requires `--experimental`. Supports both **one-shot** (a fresh, disposable VM per invocation with guaranteed teardown, via `ScriptRunner`) and **state-aware** (multi-invocation provision/start/exec/stop/deprovision, via `StatefulSandboxBackend`) modes. State-aware holds a single live VM across separate `wxc-exec` phase processes behind a persistent detached host-side daemon (`backends/windows_sandbox/daemon/`); the OS enforces a single running Windows Sandbox VM per host, so the daemon owns it and reclaims an orphaned VM on restart only via positive process-identity proof. The shared boot sequence (write per-launch nonce, launch VM, capture ownership proof, wait rendezvous, connect) lives in `backends/windows_sandbox/lifecycle/src/vm.rs::launch_managed_vm`; each mode plugs in its own `LaunchObserver` for the per-caller ownership / proof bookkeeping. Honors `readwritePaths`/`readonlyPaths`/`deniedPaths` (HOST paths) at provision via `.wsb` `<MappedFolder>` entries (mapped at the same absolute host path inside the guest; rejects `deniedPaths` equal-to or nested-within a mapped share since `.wsb` has no Deny primitive); filesystem policy is immutable post-provision. Network isolation is enforced unconditionally by the in-guest agent; `network`/`ui` and the Entra `user` bundle are not honored. ID prefix `wsb` (strict `wsb:<8-hex>` grammar). Per-launch handshake: 32-byte `Nonce` + 1-byte `ChannelRole` tag on every TCP connection (boot + reconnect); the guest pairs accepted sockets by declared role, not by accept order. The guest agent binary `wxc-windows-sandbox-guest.exe` (`backends/windows_sandbox/guest/`) is injected into the VM. |
114114
| MicroVM (NanVix) | `wxc-exec.exe` | Windows | `backends/nanvix/runner/src/lib.rs` — feature-gated behind `microvm` |
115115
| Hyperlight | `wxc-exec.exe` | Windows | `backends/hyperlight/common/src/lib.rs` — Hyperlight + Unikraft micro-VM backend |
116-
| IsolationSession | `wxc-exec.exe` | Windows | `backends/isolation_session/common/src/` — feature-gated behind `isolation_session`, experimental, uses the in-proc `Windows.AI.IsolationSession.Preview` `IsoSessionOps` API. Supports both one-shot (single-invocation lifecycle, via `ScriptRunner`) and state-aware (multi-invocation provision/start/exec/stop/deprovision, via `StatefulSandboxBackend`) modes. Rejects all filesystem policy (`readwritePaths`/`readonlyPaths`/`deniedPaths`) plus network and proxy policy at every phase with `policy_validation` — the backend has no host-folder-sharing, network, or proxy primitive. State-aware additionally accepts an optional `user` bundle (`upn`, `wamToken`) at provision and start to provision Entra cloud-agent sandboxes; one-shot rejects the bundle, and hosts that don't support Entra agents surface `backend_unavailable`. Streams stdout/stderr, forwards stdin, and switches to ConPTY mode when wxc-exec's stdout is a TTY for `spawnSandbox` parity. |
116+
| IsolationSession | `wxc-exec.exe` | Windows | `backends/isolation_session/common/src/` — feature-gated behind `isolation_session`, experimental, uses the in-proc `Windows.AI.IsolationSession.Preview` `IsoSessionOps` API. Supports both one-shot (single-invocation lifecycle, via `ScriptRunner`) and state-aware (multi-invocation provision/start/exec/stop/deprovision, via `StatefulSandboxBackend`) modes. Rejects all filesystem policy (`readwritePaths`/`readonlyPaths`/`deniedPaths`) at every phase with `policy_validation` — the backend has no host-folder-sharing primitive. The container's network is unrestricted (outbound open; a process inside can listen on a localhost-reachable port) and MXC has no primitive to filter or deny it, so provision (and one-shot) accept ONLY the canonical unrestricted-network acknowledgment — `network.defaultPolicy=allow` + `network.allowLocalNetwork=true`, no host rules, no proxy, default enforcement — and refuse anything else (including an absent policy, which defaults to the unenforceable deny) with `policy_validation`; post-provision phases reject any supplied network policy (fixed at provision, tracked via `ExecutionRequest.network_specified`) and inherit an absent one. State-aware additionally accepts an optional `user` bundle (`upn`, `wamToken`) at provision and start to provision Entra cloud-agent sandboxes; one-shot rejects the bundle, and hosts that don't support Entra agents surface `backend_unavailable`. Streams stdout/stderr, forwards stdin, and switches to ConPTY mode when wxc-exec's stdout is a TTY for `spawnSandbox` parity. |
117117
| LXC | `lxc-exec` | Linux | `core/lxc/src/main.rs` + `backends/lxc/common/` |
118118
| Seatbelt | `mxc-exec-mac` | macOS | `core/mxc_darwin/src/main.rs` + `backends/seatbelt/common/` — uses macOS App Sandbox (Seatbelt) profiles for process containment. Requires schema `0.7.0-alpha`+. Supports `network.proxy` via the same cooperative env-var model as Bubblewrap (injects `HTTP_PROXY`/`HTTPS_PROXY` into the sandbox, reusing `wxc_common::unix_proxy_coordinator`; `builtinTestServer` spawns the shared `unix-test-proxy`). See `docs/macos-support/seatbelt-backend.md`. |
119119
| Bubblewrap | `lxc-exec` | Linux | `backends/bubblewrap/common/src/bwrap_runner.rs` — unprivileged sandboxing via Linux user namespaces and `bwrap`. Experimental — requires `--experimental`. Uses shared filesystem/network policy fields; per-host network filtering via `NetworkIptablesManager` from `backends/lxc/common`. See `docs/bwrap-support/bubblewrap-backend.md`. |

docs/isolation-session/oneshot.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -239,16 +239,16 @@ versions and stating that the bindings must be regenerated.
239239

240240
**Automated (`cargo test`, runs on any machine including CI):**
241241

242-
| Category | Count | Location | What it verifies |
243-
|---|---:|---|---|
244-
| Config parsing | ~8 | `config_parser.rs` | `"isolation_session"` containment value and `experimental.isolation_session` section parsing |
245-
| Policy validation | ~15 | `policy.rs` | Every filesystem field (`readwritePaths` / `readonlyPaths` / `deniedPaths`), network, and proxy policy is rejected at every phase |
246-
| Option building | ~6 | `isolation_session_runner.rs` | `ExecutionRequest``ProcessOptions` mapping (timeout, cwd, env vars, redirect flags) |
247-
| Feature unavailable | 1 | `isolation_session_runner.rs` | Runner returns a clean error on machines without the IsolationSession feature enabled, so the test passes everywhere |
248-
249-
These ~22 backend-specific tests run alongside the existing workspace tests
250-
(287 total currently passing). The feature-unavailable test is what runs in
251-
CI, since CI machines do not have a Windows build with the IsolationSession feature enabled.
242+
| Category | Location | What it verifies |
243+
|---|---|---|
244+
| Config parsing | `config_parser.rs` | `"isolation_session"` containment value and `experimental.isolation_session` section parsing |
245+
| Policy validation | `policy.rs` | Filesystem fields (`readwritePaths` / `readonlyPaths` / `deniedPaths`) are rejected at every phase; the network policy must be the canonical unrestricted-network acknowledgment (`defaultPolicy=allow` + `allowLocalNetwork=true`, no host rules or proxy) at provision, and any supplied network policy is rejected post-provision |
246+
| Option building | `isolation_session_runner.rs` | `ExecutionRequest``ProcessOptions` mapping (timeout, cwd, env vars, redirect flags) |
247+
| Feature unavailable | `isolation_session_runner.rs` | Runner returns a clean error on machines without the IsolationSession feature enabled, so the test passes everywhere |
248+
249+
These backend-specific tests run alongside the existing workspace tests. The
250+
feature-unavailable test is what runs in CI, since CI machines do not have a
251+
Windows build with the IsolationSession feature enabled.
252252

253253
**Integration tests (require a Windows host with the IsolationSession feature enabled):**
254254

docs/isolation-session/state-aware-rust.md

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -111,19 +111,28 @@ described in [Idempotence](#idempotence-per-phase).
111111
## Cross-cutting policy honor matrix
112112

113113
IsolationSession rejects every `policy.filesystem` field (`readwritePaths`,
114-
`readonlyPaths`, `deniedPaths`), all `policy.network` policy, and
115-
`policy.network.proxy` at every phase — provision included. The backend has
116-
no host-folder-sharing, network, or proxy primitive, so there is nothing to
117-
honor. The only caller-supplied knob it accepts is the optional Entra `user`
118-
bundle, at provision and start.
114+
`readonlyPaths`, `deniedPaths`) at every phase — provision included. The
115+
backend has no host-folder-sharing primitive, so there is nothing to honor.
116+
117+
The container's network is unrestricted (outbound open; a process inside can
118+
listen on a port reachable from outside via localhost) and MXC has no
119+
primitive to filter or deny it. So the network policy is honesty-gated rather
120+
than silently accepted: **provision** (and one-shot) accept only the canonical
121+
unrestricted-network acknowledgment — `defaultPolicy=allow` +
122+
`allowLocalNetwork=true`, no `allowedHosts`/`blockedHosts`, no proxy, default
123+
enforcement — and refuse anything else, including an absent policy (which
124+
defaults to the unenforceable `block`). On the **post-provision** phases the
125+
network posture is fixed at provision, so any supplied network policy is
126+
rejected and an absent one is inherited. The only other caller-supplied knob
127+
it accepts is the optional Entra `user` bundle, at provision and start.
119128

120129
| Field | provision | start | exec | stop | deprovision |
121130
|---|---|---|---|---|---|
122131
| `policy.filesystem.{readwritePaths,readonlyPaths}` | rejected | rejected | rejected | rejected | rejected |
123132
| `policy.filesystem.deniedPaths` | rejected | rejected | rejected | rejected | rejected |
124-
| `policy.network.{allowedHosts,blockedHosts,defaultPolicy}` | rejected | rejected | rejected | rejected | rejected |
125-
| `policy.network.proxy` | rejected | rejected | rejected | rejected | rejected |
126-
| `policy.ui` | rejected | rejected | rejected | rejected | rejected |
133+
| `policy.network` — canonical `allow` acknowledgment (`defaultPolicy=allow` + `allowLocalNetwork=true`, no host rules, no proxy, default enforcement) | **required** | rejected | rejected | rejected | rejected |
134+
| `policy.network` — any other value (incl. absent → `block`, host rules, proxy) | rejected | rejected | rejected | rejected | rejected |
135+
| `policy.ui` — see the known gap below | ignored | ignored | ignored | ignored | ignored |
127136
| `experimental.isolation_session.{provision,start}.user` | **honored** | **honored** | n/a | n/a | n/a |
128137

129138
Rejection of `policy.*` fields surfaces as `error.code = "policy_validation"`.
@@ -146,11 +155,20 @@ user it assigned at provision.
146155
### Policy fields and mode parity
147156

148157
Both modes share the same policy matrix above. Every `policy.filesystem`
149-
field (`readwritePaths`, `readonlyPaths`, `deniedPaths`), all `policy.network`
150-
policy, `policy.network.proxy`, and `policy.ui` are rejected at every phase —
151-
the backend has no host-folder-sharing, network, or proxy primitive. One-shot
152-
enforces this via `validate_runner`; state-aware enforces it via the
153-
`validate_<phase>` hooks.
158+
field (`readwritePaths`, `readonlyPaths`, `deniedPaths`) is rejected at every
159+
phase (no host-folder-sharing primitive). The network policy is honesty-gated
160+
per the matrix — provision requires the canonical unrestricted-network
161+
acknowledgment and post-provision rejects any supplied network policy
162+
(inheriting an absent one). One-shot enforces this via `validate_runner`;
163+
state-aware enforces it via the `validate_<phase>` hooks.
164+
165+
**Known gap — `policy.ui` is silently ignored.** The backend has no
166+
UI-restriction primitive, and it does not validate `policy.ui`, so a supplied
167+
UI policy is accepted and then dropped rather than refused. That is the same
168+
false-guarantee shape the network honesty gate closes: a caller asking for a
169+
UI restriction gets no error and no enforcement. Rejecting `policy.ui` is the
170+
intended end state, not a deliberate exemption — the matrix records `ignored`
171+
because that is the current behavior, not the desired one.
154172

155173
### Fields valid in state-aware only
156174

docs/isolation-session/state-aware-typescript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ contract (including fields not yet exposed via the SDK).
4747
| Field | Type | Default | Description |
4848
|---|---|---|---|
4949
| `version` | string | SDK `SUPPORTED_VERSION` | Schema-version override. |
50+
| `network` | `{ defaultPolicy: 'allow'; allowLocalNetwork: true }` | — (**required**) | Unrestricted-network acknowledgment. The container runs on a network MXC cannot filter or deny (outbound open; a process inside can listen on a port reachable from outside via localhost), so the caller must explicitly acknowledge it. This exact value is the only one accepted; any other network policy (or omission) is rejected at provision, and `network` is not accepted on the post-provision phases (the posture is fixed at provision). |
5051
| `user` | `IsolationSessionUserConfig` | absent | Optional Entra credentials (see below). |
5152

5253
**Metadata (`IsolationSessionProvisionMetadata`):**

docs/sandbox-policy/v2/networking.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ Same model and enforcement as WSLc (model 2 achievable; iptables/nftables on the
317317
### Other backends
318318

319319
- **Windows Sandbox:** Guest-side firewall only, with hardcoded rules. In GA for development/testing scenarios where network isolation is not critical.
320-
- **Isolation Session:** No network configuration support (all network/proxy fields rejected at validation time). In GA for process isolation only (filesystem, identity, lifecycle).
320+
- **Isolation Session:** No network filtering or denial is possible — outbound is open and a process inside can listen on a localhost-reachable port. Provision therefore requires the canonical unrestricted-network acknowledgment (`network.defaultPolicy=allow` + `network.allowLocalNetwork=true`) and rejects every other network/proxy policy at validation time. In GA for process isolation only (identity, lifecycle).
321321
- **Hyperlight, Nanvix:** Not in this GA scope doc. Additional follow up is needed to confirm their capabilities and whether they align with this doc.
322322

323323
## Gaps and limitations

docs/state-aware-lifecycle/mxc-state-aware-sandbox-api-overview.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,7 @@ Provision and exec — the two most distinctive shapes. Reference §7.4 has all
290290

291291
```typescript
292292
const config: IsolationSessionProvisionConfig = {
293-
filesystem: { readwritePaths: ['C:\\workspace'] },
294-
network: { defaultPolicy: 'allow', allowedHosts: ['api.anthropic.com'] },
293+
network: { defaultPolicy: 'allow', allowLocalNetwork: true },
295294
};
296295
const { sandboxId } = await provisionSandbox(
297296
'isolation_session',
@@ -306,8 +305,7 @@ const { sandboxId } = await provisionSandbox(
306305
"version": "0.6.0-alpha",
307306
"containment": "isolation_session",
308307
"phase": "provision",
309-
"filesystem": { "readwritePaths": ["C:\\workspace"] },
310-
"network": { "defaultPolicy": "allow", "allowedHosts": ["api.anthropic.com"] }
308+
"network": { "defaultPolicy": "allow", "allowLocalNetwork": true }
311309
}
312310
```
313311

@@ -370,8 +368,9 @@ Cross-backend exec fields flow through top-level `process`. Cross-cutting fields
370368
(`filesystem` / `network` / `ui`) on the per-(backend, phase) Config map directly to
371369
top-level wire fields (backend declares per-phase honor per reference §10.3). The
372370
SDK Config exposes only the cross-cutting fields the runtime currently honors —
373-
for IsolationSession at provision today that's `filesystem`; `network` and `ui` are
374-
added when the runtime honors them.
371+
for IsolationSession at provision that's `network` — the required unrestricted-network
372+
acknowledgment (`{ defaultPolicy: 'allow', allowLocalNetwork: true }`); `filesystem` is
373+
rejected.
375374

376375
## Error codes
377376

docs/state-aware-lifecycle/mxc-state-aware-sandbox-api.md

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -268,9 +268,10 @@ type StateAwareContainmentBackend = Extract<ContainmentBackend, 'isolation_sessi
268268

269269
interface IsolationSessionProvisionConfig {
270270
version?: string;
271-
filesystem?: FilesystemConfig;
272-
network?: NetworkConfig;
273-
ui?: UiConfig;
271+
// IsolationSession cannot filter or deny the container network, so provision
272+
// requires the canonical unrestricted-network acknowledgment — the only accepted
273+
// value. filesystem policy is rejected by this backend (§10.3).
274+
network: { defaultPolicy: 'allow'; allowLocalNetwork: true };
274275
}
275276

276277
interface IsolationSessionStartConfig {
@@ -500,18 +501,14 @@ import {
500501
execInSandboxAsync,
501502
stopSandbox,
502503
deprovisionSandbox,
503-
getAvailableToolsPolicy,
504504
IsolationSessionProvisionConfig,
505505
SandboxSpawnOptions,
506506
} from '@microsoft/mxc-sdk';
507507

508-
const tools = getAvailableToolsPolicy();
509508
const provisionConfig: IsolationSessionProvisionConfig = {
510-
filesystem: {
511-
readwritePaths: ['C:\\workspace', ...tools.readwritePaths],
512-
readonlyPaths: tools.readonlyPaths,
513-
},
514-
network: { defaultPolicy: 'allow', allowedHosts: ['api.anthropic.com'] },
509+
// IsolationSession cannot filter or deny the container network, so provision
510+
// requires the canonical unrestricted-network acknowledgment (the only accepted value).
511+
network: { defaultPolicy: 'allow', allowLocalNetwork: true },
515512
};
516513

517514
// IsolationSession is experimental, so every call carries `experimental: true`.
@@ -570,8 +567,10 @@ The existing policy-discovery helpers (`getAvailableToolsPolicy`,
570567
`getUserProfilePolicy`, `getTemporaryFilesPolicy`) compose with state-aware Configs
571568
unchanged. They produce `FilesystemPolicyResult` fragments — `{ readonlyPaths,
572569
readwritePaths }` — whose shape matches `FilesystemConfig`'s readonly / readwrite path
573-
arrays. Consumers merge the fragments directly into a state-aware Config's `filesystem`
574-
field, as in the §6.3 example.
570+
arrays. Consumers merge the fragments directly into the `filesystem` field of a
571+
state-aware Config for a backend that honors filesystem policy at provision (e.g.
572+
WindowsSandbox); IsolationSession rejects filesystem policy, so its provision Config
573+
omits it.
575574

576575
## 7. Wire contract
577576

@@ -795,8 +794,7 @@ shape, across all five phases.
795794

796795
```typescript
797796
const config: IsolationSessionProvisionConfig = {
798-
filesystem: { readwritePaths: ['C:\\workspace'] },
799-
network: { defaultPolicy: 'allow', allowedHosts: ['api.anthropic.com'] },
797+
network: { defaultPolicy: 'allow', allowLocalNetwork: true },
800798
};
801799
const { sandboxId } = await provisionSandbox(
802800
'isolation_session',
@@ -811,8 +809,7 @@ const { sandboxId } = await provisionSandbox(
811809
"version": "0.6.0-alpha",
812810
"containment": "isolation_session",
813811
"phase": "provision",
814-
"filesystem": { "readwritePaths": ["C:\\workspace"] },
815-
"network": { "defaultPolicy": "allow", "allowedHosts": ["api.anthropic.com"] }
812+
"network": { "defaultPolicy": "allow", "allowLocalNetwork": true }
816813
}
817814
```
818815

@@ -1560,9 +1557,9 @@ documented in the backend's plan doc):
15601557

15611558
| Field | provision | start | exec | stop | deprovision |
15621559
|---|---|---|---|---|---|
1563-
| `filesystem` | applied | rejected | rejected | rejected | rejected |
1560+
| `filesystem` | rejected | rejected | rejected | rejected | rejected |
15641561
| `network` | applied | rejected | rejected | rejected | rejected |
1565-
| `ui` | applied | rejected | rejected | rejected | rejected |
1562+
| `ui` | ignored | ignored | ignored | ignored | ignored |
15661563

15671564
For WindowsSandbox, filesystem policy (readwrite/readonly/denied HOST paths) is
15681565
applied at provision and frozen for the life of the sandbox; later phases reject it.
@@ -1586,8 +1583,9 @@ unconditionally by the in-guest agent). WindowsSandbox has no Entra `user` bundl
15861583
the backend does not honor at that phase, and also fields the matrix would mark as
15871584
`applied` but the runtime does not yet implement. For IsolationSession's matrix
15881585
above, `IsolationSessionProvisionConfig` is the only Config that carries any
1589-
cross-cutting fields. The SDK currently exposes `filesystem` only; `network` and
1590-
`ui` will be added at provision when the IsolationSession runtime honors them.
1586+
cross-cutting fields. The SDK exposes `network` at provision — the required
1587+
unrestricted-network acknowledgment (`{ defaultPolicy: 'allow', allowLocalNetwork:
1588+
true }`), the only value the backend accepts; `filesystem` is rejected.
15911589
The start, exec, stop, and deprovision Configs carry none of these fields. Callers
15921590
cannot accidentally pass them.
15931591
- **Runtime enforcement at Rust.** The backend's `validate_<phase>` hooks reject

0 commit comments

Comments
 (0)