@microsoft/mxc-sdk
August 22, 2026 · View on GitHub
Node.js / TypeScript SDK for MXC (Microsoft eXecution Containers) — a policy-driven sandbox for running untrusted code (model output, plugins, tools) on Windows, Linux, and macOS.
Status: Public Preview. Schemas and APIs may change between minor versions until 1.0.
npm install @microsoft/mxc-sdk
import {
spawnSandboxFromConfig, createConfigFromPolicy,
getAvailableToolsPolicy, getTemporaryFilesPolicy,
getPlatformSupport,
} from '@microsoft/mxc-sdk';
if (!getPlatformSupport().isSupported) {
throw new Error('MXC not available on this host');
}
// Discover host tools (python, node, etc.) and a writable temp dir.
const tools = getAvailableToolsPolicy(process.env);
const temp = getTemporaryFilesPolicy();
const config = createConfigFromPolicy({
version: '0.6.0-alpha',
filesystem: {
readonlyPaths: tools.readonlyPaths, // PATH, PYTHONPATH, JAVA_HOME, …
readwritePaths: temp.readwritePaths, // %TEMP% / $TMPDIR
},
network: { allowOutbound: false },
timeoutMs: 30_000,
});
config.process!.commandLine = 'python -c "print(\'hello from sandbox\')"';
const child = spawnSandboxFromConfig(config, { usePty: false });
child.stdout!.on('data', (d) => process.stdout.write(d));
child.on('close', (code) => console.log('exit:', code));
Compatibility
Policy / config schema versions:
| Version | Status | Schema file |
|---|---|---|
0.4.0-alpha | Retired — below the 0.6.0-alpha floor (no longer accepted) | schemas/stable/mxc-config.schema.0.4.0-alpha.json |
0.5.0-alpha | Retired — below the 0.6.0-alpha floor (no longer accepted) | schemas/stable/mxc-config.schema.0.5.0-alpha.json |
0.6.0-alpha | Stable (minimum supported) | schemas/stable/mxc-config.schema.0.6.0-alpha.json |
0.7.0-alpha | Stable | schemas/stable/mxc-config.schema.0.7.0-alpha.json |
0.8.0-alpha | Stable (current) | schemas/stable/mxc-config.schema.0.8.0-alpha.json |
0.9.0-alpha | Dev (experimental backends, the experimental.* block, state-aware sandbox lifecycle) | schemas/dev/mxc-config.schema.0.9.0-dev.json |
Pick 0.8.0-alpha for new code on any supported platform.
Stable schemas document only the non-experimental surface. Experimental backends (
windows_sandbox,wslc,microvm,hyperlight,isolation_session), theexperimental.*block, and state-aware lifecycle live in0.9.0-dev. The parser still accepts them when paired with--experimentalregardless of which schema your config validates against — schema choice affects editor validation, not runtime behavior.
Network host allow/block lists are not implemented on Windows.
network.allowedHosts/network.blockedHostshave no enforcement on this platform — usenetwork.defaultPolicy(allow/block) ornetwork.proxyto constrain network access.
Schema 0.8 directional networking: createConfigFromPolicy accepts
network.egress / network.ingress, runtimeConfig.networkProxy, and
processContainer.network.allowedProxyPeer. Do not mix those fields with the
legacy network.allowOutbound, network.allowLocalNetwork,
network.allowedHosts, network.blockedHosts, or network.proxy fields.
createConfigFromPolicy authors either shape according to the supplied policy
version and fields. Schema 0.6 and 0.7 policies continue to produce the legacy
wire shape. With schema 0.8, omitting all network fields leaves the
network block out of the generated config; the native parser interprets that
as directional default-deny for egress, ingress, and host loopback. See the
Sandbox Policy 0.8.0 specification
for the complete cross-platform authoring shape.
Model 1 permits direct connections selected by IP/CIDR, protocol, and port rules; it does not configure an application-layer proxy. Model 2 denies direct internet access and supplies a loopback HTTP/S proxy endpoint. Backend-specific requirements determine how that proxy endpoint is made reachable.
Simple model 1 example — direct egress with L3/L4 filtering:
import {
createConfigFromPolicy,
spawnSandboxFromConfig,
} from '@microsoft/mxc-sdk';
const directConfig = createConfigFromPolicy({
version: '0.8.0-alpha',
network: {
egress: {
default: 'deny',
allow: [{
to: [{ cidr: '192.0.2.0/24' }],
ports: [{ protocol: 'tcp', port: 443 }],
}],
},
ingress: { default: 'deny', hostLoopback: 'deny' },
},
});
directConfig.process!.commandLine = 'node agent.js';
spawnSandboxFromConfig(directConfig);
Simple model 2 example — loopback HTTP/S proxy:
const proxyConfig = createConfigFromPolicy({
version: '0.8.0-alpha',
network: {
egress: { default: 'deny' },
ingress: { default: 'deny', hostLoopback: 'deny' },
},
runtimeConfig: { networkProxy: 'http://127.0.0.1:8080' },
});
proxyConfig.process!.commandLine = 'node agent.js';
spawnSandboxFromConfig(proxyConfig);
These are example configurations rather than universal backend recipes. ProcessContainer proxy configurations have additional criteria; see the ProcessContainer 0.8 proxy example. See the networking specification for all three connectivity modes and backend-specific support.
Platforms:
| Platform | Default backend | Other backends | Minimum build |
|---|---|---|---|
| Windows 11 24H2+ (verified on 25H2) | processcontainer | windows_sandbox, wslc, microvm, isolation_session | processcontainer: 26100 (24H2)isolation_session: 26340.9212 (Insider Preview) |
| Linux x64 / ARM64 | bubblewrap | lxc | — |
macOS ARM64 (schema 0.7.0-alpha+) | seatbelt | — | — |
The default processcontainer, bubblewrap, lxc, and seatbelt backends work out of the box. Experimental backends (windows_sandbox, wslc, microvm, isolation_session, hyperlight) require { experimental: true } in SandboxSpawnOptions when you spawn — see Choosing a Backend.
Hyperlight is an opt-in build flavor (Linux x64 and Windows x64) gated by the
--with-hyperlightcargo feature. Default shipped binaries do not include it; build from source withbuild.bat --with-hyperlight(Windows) or the equivalent cargo invocation on Linux.
getPlatformSupport() reports backend availability and, when the native probe can determine it, uiCapabilities: a platform-neutral view of which UI restrictions the host can enforce. This is currently populated only by the Windows native probe, where it is derived from JOB_OBJECT_UILIMIT_* support; Linux and macOS omit the field until their probes expose equivalent data.
Node.js: ≥ 18.
Three Ways to Spawn
The SDK provides three entry points. Prefer the config-based path (createConfigFromPolicy + spawnSandboxFromConfig) — it gives you backend selection, backend-specific tuning, and (with usePty: false) separated stdout/stderr.
1. Config-based — recommended
import {
createConfigFromPolicy, spawnSandboxFromConfig,
getAvailableToolsPolicy, getTemporaryFilesPolicy,
} from '@microsoft/mxc-sdk';
const tools = getAvailableToolsPolicy(process.env);
const temp = getTemporaryFilesPolicy();
const config = createConfigFromPolicy(
{
version: '0.6.0-alpha',
filesystem: {
readonlyPaths: tools.readonlyPaths,
readwritePaths: temp.readwritePaths,
},
network: { allowOutbound: true },
timeoutMs: 30_000,
},
'process', // intent: "process" | "vm" | "microvm"
);
// Add the script and any backend-specific runtime settings on the returned config.
config.process!.commandLine = 'python script.py';
// PTY mode (default) — IPty, merged stdout+stderr
const pty = spawnSandboxFromConfig(config);
pty.onData((d) => process.stdout.write(d));
pty.onExit(({ exitCode }) => console.log('exit:', exitCode));
// Pipe mode — ChildProcess with separated stdout/stderr + reliable exit codes
const child = spawnSandboxFromConfig(config, { usePty: false });
child.stdout!.on('data', (d) => process.stdout.write(d));
child.stderr!.on('data', (d) => process.stderr.write(d));
child.on('close', (code) => console.log('exit:', code));
2. spawnSandbox(script, policy, ...) — convenience
Quick path for process-isolation only (processcontainer on Windows, lxc on Linux, seatbelt on macOS). Returns a node-pty IPty with merged stdout/stderr.
import {
spawnSandbox,
getAvailableToolsPolicy, getTemporaryFilesPolicy,
} from '@microsoft/mxc-sdk';
const tools = getAvailableToolsPolicy(process.env);
const temp = getTemporaryFilesPolicy();
const pty = spawnSandbox('python script.py', {
version: '0.6.0-alpha',
filesystem: {
readonlyPaths: tools.readonlyPaths,
readwritePaths: temp.readwritePaths,
},
timeoutMs: 30_000,
});
pty.onData((d) => process.stdout.write(d));
pty.onExit(({ exitCode }) => console.log('exit:', exitCode));
3. spawnSandboxAsync(script, policy, ...) — promise-style
The await-friendly version of spawnSandbox. Same arguments, same restriction (process-isolation only), but resolves with { stdout, stderr, exitCode } instead of returning an IPty. stderr is always '' because the underlying PTY merges streams.
import {
spawnSandboxAsync,
getAvailableToolsPolicy, getTemporaryFilesPolicy,
} from '@microsoft/mxc-sdk';
const tools = getAvailableToolsPolicy(process.env);
const temp = getTemporaryFilesPolicy();
const result = await spawnSandboxAsync(
'python -c "import sys; print(sys.version)"',
{
version: '0.6.0-alpha',
filesystem: {
readonlyPaths: tools.readonlyPaths,
readwritePaths: temp.readwritePaths,
},
timeoutMs: 30_000,
},
);
console.log(result.stdout);
Tip: for agentic workloads, prefer multiple narrow sandboxes (one policy per task step) over a single broad policy. Add task-specific paths on top of the discovered base (e.g. a scoped output directory in
readwritePaths, a project source tree inreadonlyPaths, secrets indeniedPaths).
Choosing a Backend
Table of all backends and links to per-backend guides — click to expand.
SandboxPolicy is cross-platform. The backend is selected by the second argument to createConfigFromPolicy(policy, containment). Pass an abstract intent ("process", "vm", "microvm") whenever possible — the SDK and native binary resolve it to the right concrete backend for the host. Pass a concrete backend name when you need a specific runner.
| Backend | Intent | Platforms | Stable? | Guide |
|---|---|---|---|---|
processcontainer | process | Windows | ✅ | docs/process-container/guide.md |
bubblewrap | process | Linux | ✅ | docs/bwrap-support/bubblewrap-backend.md |
lxc | (concrete only) | Linux | ✅ | docs/lxc-support/lxc-backend.md |
seatbelt | process | macOS | ✅ (schema 0.7.0-alpha+) | docs/seatbelt/seatbelt-backend.md |
windows_sandbox | vm | Windows | Experimental | docs/windows-sandbox/windows-sandbox.md |
microvm | microvm | Windows | Experimental | docs/nanvix-microvm/nanvix.md — MicroVM via NanVix on Windows Hypervisor Platform |
wslc | (concrete only) | Windows | Experimental | docs/wsl/wsl-container-getting-started.md |
isolation_session | (concrete only) | Windows | Experimental | docs/isolation-session/oneshot.md |
Experimental backends require { experimental: true } in SandboxSpawnOptions:
const config = createConfigFromPolicy(policy, 'vm'); // → windows_sandbox on Windows
config.process!.commandLine = 'cmd /c whoami';
const pty = spawnSandboxFromConfig(config, { experimental: true });
Backend-specific tuning lives on the returned ContainerConfig. The full set of fields per backend is in the JSON schemas — they're the source of truth:
- Stable backends:
schemas/stable/ - Experimental backends:
schemas/dev/
Open the schema file matching your policy.version (e.g. mxc-config.schema.0.6.0-alpha.json) and look up processContainer, lxc, experimental.wslc, experimental.windows_sandbox, etc.
For Windows ProcessContainer configs, processContainer.learningMode: true
enables deny-and-record learning mode: failed accesses are logged but remain
denied. The internal learningModeLogging and permissiveLearningMode
capability names are reserved and must not be added directly to
processContainer.capabilities.
State-Aware Sandboxes
Provision once, exec many, tear down (long-lived workflows) — click to expand.
For long-lived sandboxes where you provision once, exec many times, and tear down at the end (e.g. agentic loops), use the state-aware lifecycle.
Backend support: the state-aware lifecycle is currently implemented for
isolation_session,windows_sandbox, andwslc(all Windows-only; all still experimental, so every call must pass{ experimental: true }). The one-shot spawn APIs (spawnSandbox/spawnSandboxFromConfig) are the supported path for every other backend.
import {
provisionSandbox, startSandbox, execInSandboxAsync,
stopSandbox, deprovisionSandbox,
} from '@microsoft/mxc-sdk';
// Every call takes a single options object (3rd arg). Experimental backends
// must pass `experimental: true`.
// isolation_session provision requires the unrestricted-network acknowledgment:
// the container's network cannot be filtered or denied, so you must opt in.
const { sandboxId } = await provisionSandbox(
'isolation_session',
{ network: { defaultPolicy: 'allow', allowLocalNetwork: true } },
{ experimental: true },
);
const opts = { experimental: true };
await startSandbox(sandboxId, undefined, opts);
const r1 = await execInSandboxAsync(sandboxId, { process: { commandLine: 'echo hello' } }, opts);
const r2 = await execInSandboxAsync(sandboxId, { process: { commandLine: 'whoami' } }, opts);
await stopSandbox(sandboxId, undefined, opts);
await deprovisionSandbox(sandboxId, undefined, opts);
windows_sandbox follows the same shape (substitute the containment string and provide filesystem.readwritePaths / readonlyPaths at provision if needed). See docs/windows-sandbox/windows-sandbox.md for the per-phase config matrix.
wslc follows the same shape and needs no provision config at all (it defaults to an alpine:latest container with no network). Provide filesystem.readwritePaths / readonlyPaths (mounted for the sandbox's lifetime), network.defaultPolicy: 'allow' (a bridged container; the default 'block' gives no network), and/or a backend-specific image / imageTarPath at provision; inject a cooperative network.proxy: { url } per-exec. WSLc state-aware requests default to schema 0.8.0-alpha. See docs/wsl/wslc-state-aware.md for the per-phase config matrix.
Handling failures. Every lifecycle call rejects with a typed MxcError. Branch on code first; when the failure came from an underlying platform API, the error also carries discrete diagnostic fields rather than a prose blob:
import { MxcError } from '@microsoft/mxc-sdk';
try {
await startSandbox(sandboxId, {}, { experimental: true });
} catch (err) {
if (err instanceof MxcError) {
if (err.code === 'stale_id') { /* the sandbox is gone -- re-provision */ }
console.error(err.message); // bare, human-readable
console.error(err.operation); // e.g. 'IsoSessionOps.StartSessionAsync'
console.error(err.nativeCode); // e.g. '0x80070490'
console.error(err.remediation); // the API's own fix-it hint, when it supplies one
}
}
operation, nativeCode and remediation are optional. A failure MXC raises before reaching the backend — a malformed request or id, or a policy rejection — carries only code and message.
These three are currently populated only by IsolationSession state-aware operations. Windows Sandbox has no semantic error channel to derive them from, and the one-shot surface folds the same detail into message instead, so they are uniformly absent there — always treat them as optional.
Branch program logic on code, which is a closed, versioned union. The values of operation and nativeCode are best-effort diagnostics derived from the underlying platform API and may change without a version bump — use them for telemetry, logging and diagnosis rather than control flow.
Full design and API: docs/state-aware-lifecycle/.
Policy Discovery Helpers
Auto-enumerate host tools, profile, and temp dirs — click to expand.
The SDK ships helpers that enumerate the host environment so your policy stays portable:
import {
getAvailableToolsPolicy, getUserProfilePolicy, getTemporaryFilesPolicy,
} from '@microsoft/mxc-sdk';
const tools = getAvailableToolsPolicy(process.env); // PATH, PYTHONPATH, JAVA_HOME, …
const profile = getUserProfilePolicy(); // %LOCALAPPDATA%\Programs, ~/.local/*
const tmp = getTemporaryFilesPolicy(); // %TEMP% / $TMPDIR
const policy = {
version: '0.6.0-alpha',
filesystem: {
readonlyPaths: [...tools.readonlyPaths, ...profile.readonlyPaths],
readwritePaths: tmp.readwritePaths,
},
network: { allowOutbound: false },
};
Each helper returns { readonlyPaths, readwritePaths } — merge what you want into SandboxPolicy.filesystem.
Common Pitfalls
UI is blocked by default on 0.5.0+ — some shells need it
The policy.ui block is enforced on all supported schema versions, and policy.ui.allowWindows defaults to false. Most non-interactive command-line tools work fine, but on Windows some shells make win32k system calls during startup and fail without UI access. All versions of PowerShell are affected — both Windows PowerShell 5.1 (powershell.exe) and PowerShell 7 (pwsh.exe). Set ui.allowWindows: true when launching a shell:
import { spawnSandboxFromConfig, createConfigFromPolicy } from '@microsoft/mxc-sdk';
const config = createConfigFromPolicy({
version: '0.6.0-alpha',
ui: { allowWindows: true }, // ← required for powershell.exe to start
});
config.process!.commandLine = 'powershell.exe -NoProfile -Command "Get-Date"';
const child = spawnSandboxFromConfig(config, { usePty: false });
PTY APIs merge stdout and stderr
spawnSandbox and spawnSandboxAsync use a PTY, so stderr is always empty in their result. Use spawnSandboxFromConfig(config, { usePty: false }) for separated streams.
createConfigFromPolicy leaves commandLine empty
You must set config.process!.commandLine = '…' before calling spawnSandboxFromConfig.
Default-deny applies to everything
No network field → no network. No readwritePaths → process can't write %TEMP%. No ui → no GUI. Use the discovery helpers to compose a sensible baseline.
process.cwd doesn't grant filesystem access
Setting cwd (or the workingDirectory argument) does not add that path to the policy. Add it to readonlyPaths / readwritePaths explicitly.
Troubleshooting
Common errors and what they mean — click to expand.
| Error | Cause | Fix |
|---|---|---|
MXC is not supported on this platform | getPlatformSupport() returned isSupported: false. On Linux: neither LXC nor Bubblewrap on PATH. On macOS: schema version < 0.6.0-alpha. | Install LXC/Bubblewrap, or switch to schema 0.6.0-alpha (or 0.7.0-alpha if you need state-aware lifecycle). |
wxc-exec.exe not found / lxc-exec not found | The SDK couldn't locate the native binary. | Set MXC_BIN_DIR=<dir> so <dir>/<arch>/wxc-exec.exe (or lxc-exec) exists, or pass options.executablePath explicitly. |
Invalid containment value '<x>' | containment field doesn't match the parser's accepted values. | Use one of the abstract intents (process, vm, microvm) or a concrete backend listed in Choosing a Backend. |
'<x>' containment requires experimental mode | A windows_sandbox / wslc / microvm / isolation_session / hyperlight backend was selected without the flag. | Pass { experimental: true } in SandboxSpawnOptions. |
process.commandLine starts with an unquoted Windows path containing a space | wxc-exec rejects unquoted paths with spaces at parse time. | Quote the executable: '"C:\\Program Files\\…\\foo.exe" args'. |
Experimental_CreateProcessInSandbox failed: WIN32_ERROR(...) | Native sandbox API returned an OS-level error, e.g. 448 = device feature not supported (Windows build / WIP feature not enabled). Note 120 (call not implemented / BaseContainer disabled) is now handled automatically — the default process backend falls back to AppContainer+DACL, so it no longer surfaces here. | Check the Windows build / WIP requirements for the backend you selected. |
Process exits -1 / 4294967295 with no stdout | Native binary terminated abnormally. | Re-run with options.debug: true (or options.logDir: '<dir>') to capture diagnostic logs. |
policy.version '<x>' is older than supported / newer than supported | Version is outside the SDK's accepted range. | Use 0.6.0-alpha, 0.7.0-alpha, 0.8.0-alpha, or 0.9.0-alpha. See Compatibility. |
For backend-specific errors, see the per-backend guide linked from the Choosing a Backend table.
API Surface
Every export at a glance — click to expand.
// Spawn — config-based (recommended)
createConfigFromPolicy(policy, containment?, containerName?) → ContainerConfig
spawnSandboxFromConfig(config, options?, workingDirectory?, env?) → IPty | ChildProcess
// Spawn — convenience (process containment only)
spawnSandbox(script, policy, options?, workingDirectory?, containerName?, env?) → IPty
spawnSandboxAsync(script, policy, ...) → Promise<{ stdout, stderr, exitCode }>
// State-aware lifecycle (currently `isolation_session`, `windows_sandbox`, and `wslc` — all Windows-only)
// `config` on provisionSandbox is required for backends whose provision config
// has a required member (isolation_session: the network acknowledgment) and
// optional otherwise (windows_sandbox, wslc).
provisionSandbox(containment, config, options?) → Promise<ProvisionResult>
startSandbox(sandboxId, config?, options?) → Promise<StartResult>
execInSandbox(sandboxId, config, options?) → IPty // streaming
execInSandboxAsync(sandboxId, config, options?) → Promise<ExecResult>
stopSandbox(sandboxId, config?, options?) → Promise<StopResult>
deprovisionSandbox(sandboxId, config?, options?) → Promise<DeprovisionResult>
// Platform & policy discovery
getPlatformSupport() → PlatformSupport
getAvailableToolsPolicy(env?, options?) → FilesystemPolicyResult
getUserProfilePolicy() → FilesystemPolicyResult
getTemporaryFilesPolicy(env?) → FilesystemPolicyResult
// Capability types
UiCapabilitySupport
// Errors (typed wire-format errors from wxc-exec)
ErrorCode, MxcError, MxcErrorFields
mxcErrorFromCode(code, message, details?) → MxcError
Full TypeScript definitions ship with the package (dist/index.d.ts). All exports are named exports from @microsoft/mxc-sdk.
Telemetry Consent
Telemetry is off-by-default unless the caller opts in with top-level telemetry.enabled: true and the applicable Windows consent/policy gates permit collection.
Telemetry consent behavior follows
docs/telemetry/telemetry-consent-design.md:
the SDK stays UI-agnostic, renders the canonical resource verbatim through a
host presenter, persists only explicit yes/no decisions, treats dismissal and
failures as non-grants, and never lets policy or transport failures opt a user
in.
Administrative policy
An IT administrator can still block MXC telemetry device-wide via MXC's own
registry policy setting. See
docs/telemetry/telemetry-administrative-policy.md
for the stable registry contract and interaction rules.
Further Reading
docs/schema.md— full configuration schema referencedocs/versioning.md— schema versioning model and experimental-feature lifecycledocs/examples.md— annotated configuration examples- Sandbox policy 0.8.0 — policy specification
- Backend-specific guides linked in the Choosing a Backend section above.
License
MIT. Contributions welcome — see the main MXC repository.