Skip to content
Closed
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
13 changes: 7 additions & 6 deletions .github/copilot-instructions.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ MXC ships a native container wrapper plus a TypeScript SDK β€” see the [SDK READ
| macOS ARM64 / x64 (schema `0.7.0-alpha`+) | `seatbelt` | β€” | β€” |


The stable one-shot backends (`processcontainer`, `bubblewrap`, `lxc`, and `seatbelt`) do not require experimental mode; Linux hosts also need the matching runtime installed: bwrap (Bubblewrap) for the default backend, or the lxc toolset for the lxc backend. **Experimental backends** (`windows_sandbox`, `wslc`, `microvm`, `isolation_session`, `hyperlight`) require `{ experimental: true }` in `SandboxSpawnOptions` or the `--experimental` CLI flag.
The stable one-shot backends (`processcontainer`, `bubblewrap`, `lxc`, `wslc`, and `seatbelt`) do not require experimental mode; Linux hosts also need the matching runtime installed: bwrap (Bubblewrap) for the default backend, or the lxc toolset for the lxc backend. `wslc` additionally needs a build with the `wslc` Cargo feature (`build.bat --with-wslc`) and a WSL2 host. **Experimental backends** (`windows_sandbox`, `microvm`, `isolation_session`, `hyperlight`) require `{ experimental: true }` in `SandboxSpawnOptions` or the `--experimental` CLI flag.

For which filesystem, network, and UI-restriction policy aspects the Windows `processcontainer` backend can enforce on each Windows 11 release (23H2 / 24H2 / 25H2 / 25H2+), see [Windows OS-version policy support](./docs/process-container/os-version-support.md).

Expand Down
14 changes: 6 additions & 8 deletions docs/linux-wsl-roadmap-june-2026.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,11 @@ File:line citations reference paths under `src/backends/<backend>/...` and `src/
>
> ```json
> {
> "experimental": {
> "wslc": {
> "image": "python:3.12",
> "portMappings": [
> { "windowsPort": 3000, "containerPort": 3000, "protocol": "tcp" }
> ]
> }
> "wslc": {
> "image": "python:3.12",
> "portMappings": [
> { "windowsPort": 3000, "containerPort": 3000, "protocol": "tcp" }
> ]
> }
> }
> ```
Expand Down Expand Up @@ -552,5 +550,5 @@ Item **LXC Network #24** (nftables backend) is gated on a real user signal β€” s
## Notes

- **Issue tracking**: [open issues](https://github.com/microsoft/mxc/issues?q=is%3Aissue+is%3Aopen). None of the above are filed yet.
- **Promotion path**: Bubblewrap and WSLC are both still under `experimental` in the schema; see `docs/versioning.md` for the migration mechanics required for each promotion.
- **Promotion path**: WSLC has been promoted to the stable surface β€” its settings live in the top-level `wslc` block and no longer require `--experimental`. Bubblewrap is still under `experimental` in the schema; see `docs/versioning.md` for the migration mechanics required for its promotion.
- **Labels**: re-use `Container-WSLC` and `Area-Executor-LXC`; propose adding `Container-Bubblewrap` (Bwrap #35).
23 changes: 12 additions & 11 deletions docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,18 @@ cannot mix both formats in one request.
"release": "3.19"
},

"wslc": { // WSL Container settings (Windows only)
"image": "alpine:latest", // Container image name
"imageTarPath": "C:\\images\\alpine.tar", // Import image from local tar file
"cpuCount": 4, // CPU count for WSLC session
"memoryMb": 2048, // Memory in MB for WSLC session
"gpu": false, // GPU passthrough
"storagePath": "C:\\wslc-storage", // Image store path
"portMappings": [ // Host<->container port forwarding. TCP only -- the WSLC SDK runtime returns E_NOTIMPL for UDP, so the parser hard-rejects "udp" entries with a clear message.
{ "windowsPort": 8080, "containerPort": 80, "protocol": "tcp" }
]
},

"seatbelt": { // macOS Seatbelt settings (macOS only)
"profileOverride": null, // Optional raw TinyScheme profile (escape hatch)
"guiAccess": false, // Allow GUI Mach services / IOKit / pty for window-drawing apps
Expand All @@ -200,17 +212,6 @@ cannot mix both formats in one request.
},

"experimental": { // Experimental features (requires --experimental)
"wslc": { // WSL Container settings
"image": "alpine:latest", // Container image name
"imageTarPath": "C:\\images\\alpine.tar", // Import image from local tar file
"cpuCount": 4, // CPU count for WSLC session
"memoryMb": 2048, // Memory in MB for WSLC session
"gpu": false, // GPU passthrough
"storagePath": "C:\\wslc-storage", // Image store path
"portMappings": [ // Host<->container port forwarding. TCP only -- the WSLC SDK runtime returns E_NOTIMPL for UDP, so the parser hard-rejects "udp" entries with a clear message.
{ "windowsPort": 8080, "containerPort": 80, "protocol": "tcp" }
]
},
"telemetry": { // Telemetry (Windows only)
"enabled": true // Emit TraceLogging ETW events via pure Rust tracelogging crate
}
Expand Down
7 changes: 4 additions & 3 deletions docs/state-aware-lifecycle/mxc-state-aware-sandbox-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ to learn the cross-cutting flags. Phase-specific fields on `SandboxSpawnOptions`
(`ptyOptions`, `usePty`) are honored by `execInSandbox` / `execInSandboxAsync` and
silently ignored on the other phases. State-awareness is not itself experimental β€”
`experimental: true` must be set when the targeted backend is itself experimental, just
as it is today for one-shot calls against `microvm` and `wslc`. IsolationSession is
as it is today for one-shot calls against `microvm`. IsolationSession is
experimental at the time of writing; that status is independent of the state-aware API
surface (Β§13).

Expand Down Expand Up @@ -706,8 +706,9 @@ Configs (Β§6.1), not on this wire-shape type. Raw-JSON callers writing
`ExperimentalStateAwareConfigs` directly are validated by the Rust parser and
`validate_<phase>` hooks at runtime (Β§10.1).

For one-shot calls (phase absent), `experimental.<backend>` directly holds the backend's
one-shot config object (e.g., `experimental.wslc?: WslcConfig`), as documented in
For one-shot calls (phase absent), an experimental backend's one-shot config object sits
directly under `experimental.<backend>`; a backend on the stable surface uses its
own top-level key instead (e.g. `wslc?: WslcConfig`). Both are documented in
`docs/schema.md`. The TypeScript types make this distinction structural:
`OneShotRequest.experimental` and `StateAwareRequest.experimental` have different shapes.

Expand Down
61 changes: 28 additions & 33 deletions docs/wsl/wsl-container-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
This guide walks you through setting up the WSL Container (WSLC) backend for
MXC, which lets you run Linux containers on Windows using the WSLC SDK.

> **Note:** WSLC is an **experimental** feature. It requires the `--experimental`
> CLI flag, `{ experimental: true }` in TypeScript SDK spawn options, or
> `SandboxRequest::set_experimental(true)` in the Rust SDK.
> **Note:** WSLC is a stable backend on the top-level `wslc` config block β€” it no
> longer needs `--experimental`, `{ experimental: true }`, or
> `SandboxRequest::set_experimental(true)`. It still requires a build with the
> `wslc` Cargo feature and a host that meets the prerequisites below.

## Prerequisites

Expand Down Expand Up @@ -92,7 +93,7 @@ cost once per image, not once per run.

> **Storage path consistency:** the cache lives under the WSLC
> `storage_path` (default `%TEMP%\mxc-wslc-sessions`). If your runtime
> configs override `experimental.wslc.storagePath`, pass the same
> configs override `wslc.storagePath`, pass the same
> value here with `-StoragePath` (or `--storage-path` on
> `wxc-exec.exe`), otherwise the runner will not find what you just
> pulled.
Expand All @@ -108,7 +109,7 @@ Run the included hello world example config from the repo root:

```powershell
cd <repo-root>
.\src\target\x86_64-pc-windows-msvc\release\wxc-exec.exe --experimental --debug examples\wslc_hello_world.json
.\src\target\x86_64-pc-windows-msvc\release\wxc-exec.exe --debug examples\wslc_hello_world.json
```

Expected output:
Expand All @@ -128,7 +129,7 @@ Once setup is done, the day-to-day flow is two distinct commands:

# (any number of times) execute against the cached image
.\src\target\x86_64-pc-windows-msvc\release\wxc-exec.exe `
--experimental my-config.json
my-config.json
```

This separation keeps `wxc-exec.exe` hermetic and fast at run time β€”
Expand All @@ -146,31 +147,30 @@ fields before spawning:
import { createConfigFromPolicy, spawnSandboxFromConfig } from '@microsoft/mxc-sdk';

const policy = {
version: '0.6.0-alpha',
version: '0.9.0-alpha',
network: { allowOutbound: true },
};

const config = createConfigFromPolicy(policy, 'wslc');
config.process!.commandLine = 'python3 -c "print(\'Hello from WSLC\')"';
config.experimental!.wslc!.image = 'python:3.12-alpine';
config.experimental!.wslc!.cpuCount = 2;
config.experimental!.wslc!.memoryMb = 1024;
config.wslc!.image = 'python:3.12-alpine';
config.wslc!.cpuCount = 2;
config.wslc!.memoryMb = 1024;

// PTY mode (interactive terminal):
const ptyProcess = spawnSandboxFromConfig(config, { experimental: true });
const ptyProcess = spawnSandboxFromConfig(config);

// Non-PTY mode (reliable exit codes, separate stdout/stderr):
const child = spawnSandboxFromConfig(config, { experimental: true, usePty: false });
const child = spawnSandboxFromConfig(config, { usePty: false });
child.stdout?.on('data', (data) => console.log(data.toString()));
child.on('close', (code) => console.log('Exit code:', code));
```

### Rust SDK

The Rust SDK (`mxc-sdk`) runs WSLC **in-process** β€” it does not spawn
`wxc-exec.exe`. Build the crate with its `wslc` feature, select the backend with
`build_request_with_containment`, and opt into experimental features on the
request (the library-side equivalent of `--experimental`):
`wxc-exec.exe`. Build the crate with its `wslc` feature and select the backend with
`build_request_with_containment` β€” no experimental opt-in is needed:

```toml
# Cargo.toml
Expand Down Expand Up @@ -199,9 +199,7 @@ let wslc = WslcSection {
};

let mut request = build_request_with_containment(&policy, &Containment::Wslc(wslc), None)?;
request
.set_script("python3 -c \"print('Hello from WSLC')\"")
.set_experimental(true);
request.set_script("python3 -c \"print('Hello from WSLC')\"");

// Run to completion, capturing output…
let output = run(request.clone())?;
Expand All @@ -212,7 +210,7 @@ let mut sandbox = spawn_sandbox(request)?;
let stdout = sandbox.take_stdout().expect("stdout");
```

`WslcSection` mirrors the `experimental.wslc` block below;
`WslcSection` mirrors the top-level `wslc` block below;
`WslcSection::default()` matches the SDK default (`alpine:latest`). Settings go
through the same parser the executor uses, so a rejected value (e.g. a port
mapping with a zero or duplicated host port) fails at
Expand All @@ -234,7 +232,7 @@ Notes and limits:

### JSON config

WSLC-specific settings go under `experimental.wslc` in the JSON config:
WSLC-specific settings go under the top-level `wslc` block in the JSON config:

| Field | Type | Default | Description |
|---|---|---|---|
Expand All @@ -258,7 +256,7 @@ WSLC-specific settings go under `experimental.wslc` in the JSON config:
```

```json
"experimental": { "wslc": { "image": "alpine:latest" } }
"wslc": { "image": "alpine:latest" }
```

**2. Pre-pulled from a custom registry (no auth):**
Expand All @@ -268,19 +266,17 @@ WSLC-specific settings go under `experimental.wslc` in the JSON config:
```

```json
"experimental": { "wslc": { "image": "ghcr.io/linuxserver/baseimage-alpine:3.21" } }
"wslc": { "image": "ghcr.io/linuxserver/baseimage-alpine:3.21" }
```

Tested registries: DockerHub, `mcr.microsoft.com`, `ghcr.io`, `quay.io`.

**3. Import from a local tar file (no pre-pull needed):**

```json
"experimental": {
"wslc": {
"image": "my-image:latest",
"imageTarPath": "C:\\path\\to\\image.tar"
}
"wslc": {
"image": "my-image:latest",
"imageTarPath": "C:\\path\\to\\image.tar"
}
```

Expand Down Expand Up @@ -333,14 +329,14 @@ address the container can reach:

```json
{
"version": "0.6.0-alpha",
"version": "0.9.0-alpha",
"containment": "wslc",
"process": { "commandLine": "curl -fsSL https://example.com && echo OK" },
"network": {
"defaultPolicy": "allow",
"proxy": { "url": "http://proxy.example:8080" }
},
"experimental": { "wslc": { "image": "alpine:latest" } }
"wslc": { "image": "alpine:latest" }
}
```

Expand Down Expand Up @@ -387,7 +383,7 @@ rather than refused merely for being present.
inbound connections) is **rejected at config-parse time** for WSLC. A WSLC
container runs in the NAT'd WSL2 VM and MXC does not honor a blanket
inbound-listen grant — only explicit host→container forwards via
`experimental.wslc` `portMappings` have any inbound effect, so accepting the
`wslc.portMappings` have any inbound effect, so accepting the
flag would silently promise reachability the backend never delivers. Expose
specific ports with `portMappings` instead. (`allowLocalNetwork: false`, the
default, is a no-op and is accepted.)
Expand Down Expand Up @@ -447,9 +443,8 @@ explicit `provision` / `deprovision` phases rather than by per-run flags.
| `Failed to load wslcsdk.dll` | DLL not in same directory as `wxc-exec.exe` | Copy `wslcsdk.dll` next to the binary |
| `WSLC runtime unavailable` | WSL runtime package is missing, older than 2.9.9, or the Virtual Machine Platform optional component is disabled | Update WSL with `wsl --update --pre-release`, verify the installed version with `wsl --version`, and enable the Virtual Machine Platform optional component if required. The WSLC SDK DLL is a separate dependency and does not replace the WSL runtime package. |
| `WSLC runtime unavailable. Missing components: SdkNeedsUpdate` | The opposite direction: your installed WSL is **newer** than the WSLc SDK this MXC build ships (pinned by `WSLC_SDK_VERSION` in `src/backends/wslc/common/build.rs`) | Update MXC to a build with a newer pinned SDK. Do **not** update WSL β€” it is already ahead, and updating it further will not clear this. |
| `WSLC image '<name>' not found locally` | Image was not pre-pulled, and no `imageTarPath` is set | Run `.\scripts\setup-wslc.ps1 -Image <name>` (or `wxc-exec.exe --setup-wslc --image <name>`); match the `-StoragePath` to your config's `experimental.wslc.storagePath` if set |
| `WSLC is an experimental feature` | Missing `--experimental` flag | Add `--experimental` to CLI or `{ experimental: true }` in SDK |
| `experimental mode` error in SDK | `SandboxSpawnOptions.experimental` not set | Pass `{ experimental: true }` to spawn functions |
| `WSLC image '<name>' not found locally` | Image was not pre-pulled, and no `imageTarPath` is set | Run `.\scripts\setup-wslc.ps1 -Image <name>` (or `wxc-exec.exe --setup-wslc --image <name>`); match the `-StoragePath` to your config's `wslc.storagePath` if set |
| `'experimental.wslc' has moved to the stable section` | Config still nests the block under `experimental.wslc` (pre-promotion shape) | Move the block to the top-level `wslc` key |
| Container exits with code -1 | Process failed or timed out | Check stderr output with `--debug` flag |

## Example Configs
Expand Down
Loading
Loading