Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ The Rust workspace (`src/`) implements multiple sandboxing backends behind the `
| Windows Sandbox | `wxc-exec.exe` | Windows | `backends/windows_sandbox/common/src/windows_sandbox_runner.rs` |
| MicroVM (NanVix) | `wxc-exec.exe` | Windows | `backends/nanvix/runner/src/lib.rs` β€” feature-gated behind `microvm` |
| Hyperlight | `wxc-exec.exe` | Windows | `backends/hyperlight/common/src/lib.rs` β€” Hyperlight + Unikraft micro-VM backend |
| IsolationSession | `wxc-exec.exe` | Windows | `backends/isolation_session/common/src/` β€” feature-gated behind `isolation_session`, experimental, uses the in-proc `Windows.AI.IsolationSession` `IsoSessionOps` API (loaded from `IsoSessionApp.dll`). Supports both one-shot (single-invocation lifecycle, via `ScriptRunner`) and state-aware (multi-invocation provision/start/exec/stop/deprovision, via `StatefulSandboxBackend`) modes. Honors `readwritePaths` and `readonlyPaths` at provision via `ShareFolderBatchAsync` (rejects `deniedPaths` since the API has no Deny ACE primitive); filesystem policy is immutable post-provision and rejected at later phases. 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. |
| 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. |
| LXC | `lxc-exec` | Linux | `core/lxc/src/main.rs` + `backends/lxc/common/` |
| 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`+. See `docs/macos-support/seatbelt-backend.md`. |
| 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`. |
Expand Down Expand Up @@ -153,9 +153,9 @@ State-aware lifecycle:

- `docs/state-aware-lifecycle/mxc-state-aware-sandbox-api.md` β€” state-aware sandbox lifecycle API (cross-backend wire format, Rust `StatefulSandboxBackend` trait, and dispatcher contract)
- `docs/state-aware-lifecycle/mxc-state-aware-sandbox-api-overview.md` β€” companion overview to the full state-aware design
- `docs/isolation-session/initial-bringup-plan.md` β€” IsolationSession backend, one-shot bringup (experimental, isolated user account per execution via the OS-side service)
- `docs/isolation-session/state-aware-rust-initial-plan.md` β€” IsolationSession state-aware lifecycle, Rust-layer plan (per-phase config / metadata, policy honor matrix, idempotence, concurrency, error mapping)
- `docs/isolation-session/state-aware-typescript-initial-plan.md` β€” IsolationSession state-aware lifecycle, TypeScript SDK plan
- `docs/isolation-session/oneshot.md` β€” IsolationSession backend, one-shot bringup (experimental, isolated user account per execution via the OS-side service)
- `docs/isolation-session/state-aware-rust.md` β€” IsolationSession state-aware lifecycle, Rust-layer spec (per-phase config / metadata, policy matrix, idempotence, concurrency, error mapping)
- `docs/isolation-session/state-aware-typescript.md` β€” IsolationSession state-aware lifecycle, TypeScript SDK spec

## Key Conventions

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# MXC IsolationSession Backend β€” Initial Bringup Plan
# MXC IsolationSession Backend β€” One-Shot Bringup

## Problem

Expand All @@ -11,7 +11,7 @@ session. Use cases that need this β€” per the broader claw-on-MXC scenario β€” c
- **OS-managed session lifecycle** that the OS-side service tears down cleanly when
the calling process exits, and
- a **path toward stateful execution** where one provisioned user and session
can host multiple sequential exec calls without re-paying the registration /
can host multiple sequential exec calls without re-paying the
provisioning / session-start cost each time.

## Proposed Solution
Expand All @@ -20,10 +20,10 @@ Add an **IsolationSession runner** to `wxc-exec.exe`, behind `--experimental`.
When the JSON config specifies `"containment": "isolation_session"` and the
experimental flag is set, the binary routes to a new `IsolationSessionRunner`
(implementing the existing `ScriptRunner` trait). The runner orchestrates the
full lifecycle against the OS-side Isolation Session API: register the
calling app, provision an agent user, start a session, run the script
(capturing stdout / stderr / exit code into `ScriptResponse`), then stop the
session, deprovision the agent, and unregister. All of this happens through
full lifecycle against the OS-side Isolation Session API: provision an
agent user, start a session, run the script (capturing stdout / stderr /
exit code into `ScriptResponse`), then stop the session and deprovision the
agent. All of this happens through
Rust bindings auto-generated from a private WinMD; the OS-side API is gated
on an internal Windows feature flag.

Expand All @@ -44,21 +44,19 @@ wxc-exec.exe (Rust β€” single binary, multiple backends)
β”œβ”€β”€ Parses JSON config β†’ sees containment = "isolation_session"
β”œβ”€β”€ Checks --experimental flag β†’ instantiates IsolationSessionRunner
β”œβ”€β”€ Calls IsolationSessionManager methods 1:1 with the OS-side service:
β”‚ register_client(regId) β†’ Step 0
β”‚ provision_agent_user(...) β†’ Step 1 β€” creates agent user
β”‚ start_session(..., configId) β†’ Step 2 β€” boots session
β”‚ create_process(..., path, args, opts) β†’ Step 3 β€” launches in session
β”‚ [Read stdout + stderr handles] β†’ drives ScriptResponse
β”‚ [WaitForExitAsync + ExitCode] β†’ drives exit_code
β”‚ stop_session(...) β†’ Step 4
β”‚ deprovision_agent_user(...) β†’ Step 5
β”‚ unregister_client(regId) β†’ Step 6
β”‚ add_user(...) β†’ Step 1 β€” creates agent user
β”‚ start_session(...) β†’ Step 2 β€” boots session
β”‚ create_process(...) β†’ Step 3 β€” launches in session
β”‚ [Read stdout + stderr] β†’ drives ScriptResponse
β”‚ [WaitForExitAsync + ExitCode] β†’ drives exit_code
β”‚ stop_session(...) β†’ Step 4
β”‚ deprovision_agent_user(...) β†’ Step 5
└── Returns ScriptResponse with stdout, stderr, exit_code
```

The OS-side service does the heavy lifting: it provisions a Windows agent user account
(named `<CallingUser>-IEB-<NNN>`), launches an `IsolationProxy.exe` per
session, and exposes the running script as an `IIsolationSessionWorkerProcess`
The OS-side service does the heavy lifting: it provisions a fresh per-execution
Windows agent user account (an opaque, OS-assigned name), launches a per-session
host process, and exposes the running script as an `IsoSessionProcess` handle
from which the runner reads pipe handles for I/O.

## Architecture
Expand All @@ -83,9 +81,8 @@ let mut runner: Box<dyn ScriptRunner> = match request.containment {
The runner is split into two layers:

- **`IsolationSessionManager`** β€” reusable, lifecycle methods that map 1:1
to the OS-side API. Methods: `new`, `register_client`,
`provision_agent_user`, `start_session`, `create_process`, `stop_session`,
`deprovision_agent_user`, `unregister_client`.
to the OS-side API. Methods: `new`, `add_user`, `start_session`,
`create_process`, `stop_session`, `deprovision_agent_user`.
- **`IsolationSessionRunner`** β€” thin one-shot `ScriptRunner` impl that
drives the manager's methods in order. Disposable when a stateful path
lands.
Expand Down Expand Up @@ -135,46 +132,45 @@ interface.
"timeout": 30000
},
"experimental": {
"isolation_session": {
"configurationId": "small"
}
"isolation_session": {}
}
}
```

The only `experimental.isolation_session` knob is `configurationId`, which
selects the OS-side session size: `"small"` (1, default), `"medium"` (2),
`"large"` (3), or `"commandline"` (4). Other process options (`cwd`, `env`,
`timeout`) read from the existing top-level `process` section, matching the
contract every other backend honors.
The one-shot `experimental.isolation_session` block currently carries no
honored knobs β€” the Entra `user` bundle is a state-aware-only field and is
rejected on the one-shot path. Process options (`cwd`, `env`, `timeout`) read
from the existing top-level `process` section, matching the contract every
other backend honors.

Run with: `wxc-exec.exe --experimental config.json`.

## OS API Dependency

The runner calls into the WinRT API namespaced
`Windows.AI.IsolationEnvironment.Session`, exposed by the OS-side Isolation
`Windows.AI.IsolationSession.Preview`, exposed by the OS-side Isolation
Session service (running as SYSTEM via `svchost.exe`). The API is gated
on an internal Windows feature flag.

Activation goes through
`RoGetActivationFactory(RuntimeClass_Windows_AI_IsolationEnvironment_Session_IsolationSessionClient)`.
Activation goes through the WinRT activation factory for the
`Windows.AI.IsolationSession.Preview` `IsoSessionOps` runtime class.
Activation requires `RoInitialize(RO_INIT_MULTITHREADED)` (handled in
`main.rs` at startup, applied unconditionally because it's benign for other
backends).

The API surface includes seven lifecycle methods plus
`IIsolationSessionWorkerProcess` (the running-process handle). The runner
uses a minimal subset of the worker-process surface: stdout pipe, stderr
pipe, `WaitForExitAsync`, `ExitCode`. It does not use stdin, terminate,
The API surface includes the lifecycle methods plus
`IsoSessionProcess` (the running-process handle). The runner
uses a minimal subset of the process surface: stdout pipe, stderr
pipe, exit-wait, and exit code. It does not use stdin, terminate,
control signals, or interactive ConPTY mode.

## Bindings Workflow

**Why a private WinMD.** The OS-side API ships its WinMD
(`windows.ai.isolationenvironment.winmd`) as part of an internal Windows OS
build. There is no public NuGet or release distribution today. MXC stores
generated Rust bindings in the workspace and tracks their provenance.
**Why a private WinMD.** The OS-side API ships its WinMD as part of an
internal Windows OS build (the exact file name and provenance are recorded
in `GENERATION_INFO.toml`). There is no public NuGet or release distribution
today. MXC stores generated Rust bindings in the workspace and tracks their
provenance.

**Future direction.** The OS API is expected to land in the public Windows
SDK eventually, at which point the `windows` crate (auto-generated from
Expand All @@ -194,15 +190,12 @@ moves), the bindings must be regenerated by a Microsoft engineer with
access to the private WinMD. `windows-bindgen` X.Y generates code that
targets the `windows` X.Y crate, so a regenerator must use a
`windows-bindgen` release whose major.minor matches the workspace
`windows` crate. For avoidance of doubt: although earlier draft text in
this document may refer to the WinRT namespace
`Windows.AI.IsolationEnvironment.Session`, the generated bindings and the
Rust code in this repo use `Windows.AI.IsolationSession` (for example,
`IsoSessionOps`), and that is the naming to use when diagnosing
regeneration or version-coupling issues. The build-time check below
catches the most common slip β€” bumping the workspace `windows` crate
without regenerating β€” by comparing the workspace version against the
recorded `target_windows_crate`.
`windows` crate. The generated bindings and the Rust code in this repo use
the `Windows.AI.IsolationSession.Preview` namespace (for example,
`IsoSessionOps`) β€” that is the naming to use when diagnosing regeneration or
version-coupling issues. The build-time check below catches the most common
slip β€” bumping the workspace `windows` crate without regenerating β€” by
comparing the workspace version against the recorded `target_windows_crate`.

**`build.rs` version check.** `isolation_session_bindings/build.rs` reads
the expected `windows` crate version from `GENERATION_INFO.toml`
Expand All @@ -214,17 +207,15 @@ versions and stating that the bindings must be regenerated.

**Implemented:**

- Single-shot `register β†’ provision β†’ start β†’ run β†’ stop β†’ deprovision β†’
unregister` lifecycle, gated by `--experimental`.
- Single-shot `provision β†’ start β†’ run β†’ stop β†’ deprovision` lifecycle,
gated by `--experimental`.
- `process.commandLine` (the script command, wrapped via `cmd.exe /c "..."`
β€” the same pattern the LXC runner uses with `/bin/sh -c`).
- `process.cwd` (working directory inside the session).
- `process.env` (environment variables forwarded via
`IIsolationSessionWorkerProcessCreateOptions::SetEnvironmentVariables`).
- `process.env` (environment variables forwarded via the OS-side
`IsoSessionProcessOptions`).
- `process.timeout` (forwarded to the OS-side per-process timeout
enforcement).
- `experimental.isolation_session.configurationId`
(Small / Medium / Large / CommandLine).
- `lifecycle.destroyOnExit` (mapped to the OS-side `LifetimePolicy`: `true` β†’
`CallerProcess`, `false` β†’ `Indefinite`; matches how other backends
interpret this field).
Expand All @@ -250,8 +241,8 @@ versions and stating that the bindings must be regenerated.

| Category | Count | Location | What it verifies |
|---|---:|---|---|
| Config parsing | ~8 | `config_parser.rs` | `"isolation_session"` containment value, `experimental.isolation_session` section, `configurationId` values + defaults |
| Policy validation | ~15 | `isolation_session_runner.rs` | Phase-specific behaviour: provision accepts `readwritePaths` / `readonlyPaths` and rejects `deniedPaths`; non-provision phases reject every filesystem field; network and proxy are rejected at every phase |
| Config parsing | ~8 | `config_parser.rs` | `"isolation_session"` containment value and `experimental.isolation_session` section parsing |
| Policy validation | ~15 | `policy.rs` | Every filesystem field (`readwritePaths` / `readonlyPaths` / `deniedPaths`), network, and proxy policy is rejected at every phase |
| Option building | ~6 | `isolation_session_runner.rs` | `ExecutionRequest` β†’ `ProcessOptions` mapping (timeout, cwd, env vars, redirect flags) |
| Feature unavailable | 1 | `isolation_session_runner.rs` | Runner returns a clean error on machines without the IsolationSession feature enabled, so the test passes everywhere |

Expand All @@ -265,7 +256,7 @@ Two end-to-end configs live under `tests/configs/`:

- `isolation_session_hello.json` β€” happy path. Prints `USERNAME`,
`MYVAR`, `CWD`, and `whoami` from inside the session. Validates the
agent identity (`<calling-user>-IEB-<NNN>`), env-var pass-through,
agent identity (the freshly-provisioned agent account), env-var pass-through,
working-directory pass-through, and that the running account differs
from the caller.
- `isolation_session_exit42.json` β€” runs `exit 42` and validates that
Expand Down Expand Up @@ -318,7 +309,7 @@ The following were observed during VM testing and are accepted for v0.1.
| OS API not present on older Windows builds | the IsolationSession feature is OS-side; runner reports a clean error when the activation factory fails. Feature-unavailable test exercises this on CI |
| New Cargo feature increases coupling | The `isolation_session` feature is off by default in the workspace; default builds and existing CI are unaffected |
| Manual VM testing required | The OS-side service has the same constraint for any consumer (it rejects network-logon tokens). Automated suite covers what it can without the OS-side service |
| One-shot lifecycle is heavy (full register β†’ provision β†’ start per call) | Accepted for v0.1; experimental flag indicates rough edges. Stateful API is the planned mitigation |
| One-shot lifecycle is heavy (full provision β†’ start per call) | Accepted for v0.1; experimental flag indicates rough edges. Stateful API is the planned mitigation |
| `ProvisionAgentUserAsync` re-provision hang under `Indefinite` lifetime | Manager calls `GetAgentUser` first and skips a redundant provision when the user already exists |
| `DeprovisionAgentUserAsync` failure under `Indefinite` lifetime | Manager re-provisions with `CallerProcess` lifetime as part of teardown so the OS-side process-exit callback handles cleanup naturally |

Expand All @@ -327,8 +318,9 @@ The following were observed during VM testing and are accepted for v0.1.
**For end users:**

- A Windows build with the IsolationSession feature enabled.
- `IsolationProxy.exe` present in `%SystemRoot%\System32\` (ships with
Windows as part of the OS-side service).
- The OS-side isolation-session host binary present in
`%SystemRoot%\System32\` (ships with Windows as part of the OS-side
service).
- WinRT initialized as MTA (handled by `wxc-exec`).

**For developers:**
Expand Down Expand Up @@ -358,13 +350,13 @@ wxc-exec.exe --experimental hello.json
"timeout": 30000
},
"experimental": {
"isolation_session": { "configurationId": "small" }
"isolation_session": {}
}
}
```

Expected stdout: `<host>\<caller>-ieb-<nnn>` (the freshly-provisioned agent
user, distinct from whichever account ran `wxc-exec`).
Expected stdout: the freshly-provisioned agent account name (an opaque,
OS-assigned account, distinct from whichever account ran `wxc-exec`).

## Supported Workloads

Expand Down
Loading
Loading