MXC Learning mode records every file, network, and capability access your coding agent was denied, then hands you a JSON list you can turn into a least-privilege sandbox policy - one that grants only the access the task needs. It ships in Microsoft Execution Containers (MXC) 1.0, released 2026-10-07 and already used by GitHub Copilot and OpenAI Codex. The catch: the denial report works only on Windows, and the obvious shortcut, wxc-exec --audit, switches containment off while it records.
Read on if you run a coding agent on Windows, or build a harness that has to sandbox agents on Windows, macOS, and Linux from one config. On a Mac with Claude Code only? Jump to what MXC does not contain. Claude Code doesn’t support MXC yet.
What MXC is
MXC (Microsoft Execution Containers) is an MIT-licensed SDK for Rust, .NET, and Node that takes one JSON policy - which files, network destinations, processes, and UI the workload may touch - and enforces it with the operating system’s own sandbox. The policy lives outside the agent, so the agent, its generated code, or a plugin “cannot grant itself additional access.”
MXC is not a new isolation technology. It’s one API in front of the ones each OS already has:
- AppContainer - Windows’ built-in process sandbox.
- Seatbelt - macOS’s kernel sandbox, the same one Claude Code’s
/sandboxuses on a Mac. - Bubblewrap - a Linux tool that runs a process in an isolated view of the system that sees only the folders you hand it; also what
/sandboxuses on Linux.
| Platform | Default backend | Other backends |
|---|---|---|
| Windows 11 (x64, ARM64) | processcontainer (AppContainer) | wslc, isolation_session; experimental: windows_sandbox, microvm, hyperlight |
| Linux (x64, ARM64) | bubblewrap | lxc, microvm, hyperlight |
| macOS (ARM64, x64) | seatbelt | none |
The session container (isolation_session) is Windows-only: it runs the agent under a separate Windows account with its own desktop, clipboard, and input. Microsoft pitches it for long-running agents that need a desktop or stronger separation from the person at the keyboard.
The policy: five areas
| Area | Controls |
|---|---|
| Containment | Which backend runs the workload |
| Process | Command, arguments, working directory, environment |
| File system | readwritePaths, readonlyPaths, deniedPaths |
| Network | Inbound and outbound rules, host loopback |
| User interface | Desktop, clipboard, display access |
The smallest useful request, from the MXC README:
import { spawn, type ContainerRequest } from '@microsoft/mxc-sdk/v1';
const request: ContainerRequest = {
command: 'node -e "console.log(\'hello from container\')"',
network: { egress: { default: 'deny' } },
timeoutMs: 30_000,
};
const child = await spawn(request);
Everything is deny-by-default. Touch something the policy doesn’t grant and the OS returns a plain “Access is denied”, and the tool fails. Microsoft’s own post names the problem: writing a least-privilege policy is hard when you don’t yet know every resource the workload needs. An agent’s workload changes with every task.
Three modes, and only two contain
MXC’s answer is to record what was blocked. It has three modes:
| Mode | Ungranted access | Activity report | Platforms |
|---|---|---|---|
| Enforcement | Blocked | No | All |
| Learning | Blocked and recorded | Yes | Windows ProcessContainer only |
| Permissive | Allowed and recorded | Yes | Windows ProcessContainer only |
Two traps in that table.
Learning is Windows-only. MXC’s docs say the learning-mode capabilities “are Windows-only” and apply to the AppContainer-based backends; “on other platforms the capability strings are ignored.” On a Mac or Linux box you get enforcement and no report.
--audit is Permissive, not Learning. The README’s warning is blunt: --audit “turns off all sandbox security for the workload being analyzed. Never use it to run untrusted code.” It’s built for a developer bringing up their own trusted tool. A coding agent that may have read a prompt-injected README is not a trusted tool.
Draft a policy from the denial log (Windows)
Use deny-and-record: the agent behaves exactly as it will in production, and you get a list of what it reached for.
-
Start from a tight policy. Grant the repository read-write, deny outbound network, and turn on
captureDenialsinblockmode. Field names below come from the Node V1 types and the officialrun-with-logging-access-deniedsample:import { run, type ContainerRequest } from '@microsoft/mxc-sdk/v1'; const repo = 'C:\\src\\my-repo'; const request: ContainerRequest = { command: 'npm test', containment: { type: 'processcontainer', config: { captureDenials: { mode: 'block', outputPath: 'C:\\logs\\denials.json' }, }, }, filesystem: { readwritePaths: [repo] }, workingDirectory: repo, network: { egress: { default: 'deny' } }, timeoutMs: 600_000, }; const result = await run(request); console.log(result.outputMetadata?.captureDenials);C:\logsmust already exist. MXC inserts a per-run ID into the filename (denials.<run-id>.json), so read the real path fromoutputMetadata.captureDenials.outputPath. You may also seeprocessContainer.learningMode: truein the docs: it turns on the same deny-and-record mode, whilecaptureDenialsis the switch that also collects the denials into this JSON report. UsecaptureDenials. -
Run the agent’s real task, not a toy command. The report only lists what the run actually touched.
-
Read the report. Each entry is one blocked
(resource, accessType)pair, already de-duplicated:{ "denials": [ { "resource": "C:\\Users\\test\\secret.txt", "resourceType": "file", "accessType": "read", "pid": 1234, "filetime": "132847890123456789" }, { "resource": "internetClient", "resourceType": "capability", "accessType": "unknown", "pid": 1234, "filetime": "132847890123512345" } ], "summary": { "exitCode": 0, "totalDenials": 2, "deniedResourcesTruncated": false } } -
Grant only what the task needs. A toolchain cache read goes into
readonlyPaths. A home-directory secret stays denied - that denial is the sandbox doing its job, not a gap. Acapabilityentry such asinternetClientnames a Windows AppContainer permission the workload asked for; grant it only if the task really needs it. -
Re-run until the denials are only the ones you meant. If
deniedResourcesTruncatedistrue, the run hit the cap (10,000 unique denials or 1,000,000 trace events) and the list is incomplete.
A caveat: I haven’t run this on a Windows box. The code is assembled from MXC’s Node types and its official sample, so check it against the sample first.
What MXC does not contain
MXC wraps the processes your harness launches through it. Anything the harness does in its own process is outside the box.
GitHub Copilot is the clearest worked example. With sandboxing on (/sandbox in the Copilot CLI), shell commands, local MCP servers, and language servers run inside MXC: the BaseContainer tier of ProcessContainer on Windows, Seatbelt on macOS, Bubblewrap on Linux. Built-in file tools run inside Copilot itself, where “the agent harness checks their requests against the effective policy, but those checks aren’t OS-enforced.” Remote MCP servers are outside the local sandbox too.
That’s the same shape as Claude Code’s /sandbox, which wraps Bash and its children but not Read, Edit, or MCP. The Claude Code sandboxing guide covers that gap and the isolation ladder above it.
| Agent | MXC status (2026-10-07) |
|---|---|
| GitHub Copilot | Supports it |
| OpenAI Codex | Supports it |
| Replit, OpenClaw, LM Studio, Unsloth | Support it |
| Claude Code | “Will be releasing support” |
| Hermes Agent, Manus, Perplexity, Raycast | “Will be releasing support” |
Known issues at 1.0
A week after the 1.0 release, the Windows ProcessContainer still breaks ordinary developer tools:
| Symptom | Cause | Issue |
|---|---|---|
Node fs.realpathSync.native fails with EPERM; git loses its working directory | GetFinalPathNameByHandleW(VOLUME_NAME_DOS) returns ERROR_ACCESS_DENIED (5) inside the container, whatever the file-system policy | #1464 |
cd into a read-only grant fails with ERROR_ACCESS_DENIED | Directory opens with FILE_TRAVERSE are denied on read-only paths | #1467 |
The Hacker News thread (228 points) is skeptical in the same direction. One commenter who tried it: “it’s not a 1.0 release. This is more like an early tech preview.” Another: some Windows backends “cause many exes to straight up fail during startup (powershell for instance).” Others ask why they’d use the wrapper over Bubblewrap directly - a fair question if you only ship on Linux.
When MXC is worth it
- You build a harness that runs on Windows, macOS, and Linux. One policy schema and one SDK beat three hand-written sandbox configs. That’s the actual pitch.
- You run agents on Windows. It’s the only platform where MXC gives you a denial report, and the only one with a session container that keeps the agent off your desktop.
Not worth it yet if you only use Claude Code on a Mac or Linux: /sandbox already uses Seatbelt or Bubblewrap. Climb the sandbox ladder for anything it doesn’t cover.
Next step on Windows: clone microsoft/mxc, run samples/run-with-logging-access-denied/node, and confirm your Windows build supports denial capture before you point it at an agent.