Microsoft eXecution Container (MXC) Documentation
repository·main·Indexed 22 days ago
https://github.com/microsoft/mxcA cross-platform sandboxing system for securely running untrusted code on Windows, Linux, and macOS. MXC abstracts containment backends—including processcontainer, bubblewrap, seatbelt, and experimental options like wslc and microvm—via a unified JSON configuration schema and a TypeScript SDK (@microsoft/mxc-sdk). The system supports native binary execution, state-aware lifecycle management, and audit mode for policy discovery on Windows.
What's inside Microsoft eXecution Container (MXC)
- MXC is a sandboxed code execution system designed to run untrusted code (such as model outputs, plugins, or tools) securely on Windows, Linux, and macOS. It abstracts various containment backends—ranging from OS-native process sandboxes to full Virtual Machines—behind a unified JSON configuration schema and a TypeScript SDK.
What is Permissive Learning Mode (PLM)?
mainPermissive Learning Mode (PLM) is a Windows-only trace driver (
plm.exe) used to facilitate the transition from an unconstrained workload to an enforced MXC container configuration.It works by capturing access-denied events emitted by the Windows permissive sandbox layer. The tool decodes these events into structured findings and automatically merges them into a copy of your existing MXC container configuration. This allows you to generate an
Adjusted_<name>.jsonconfig file that includes the necessaryfilesystem.readwritePathsandfilesystem.readonlyPathsso that subsequent enforcing runs succeed without access errors.Understand Bubblewrap filesystem isolation behavior
mainBubblewrap (Bwrap) provides filesystem isolation through namespace enforcement with the following characteristics:
- Default-deny: If no
--bindis specified, there is no access to the host filesystem. The base environment is a curated allowlist (defined byBASELINE_RO_BIND_PATHS) rather than a full bind of the host root (/). - Subtree Mounting: Using
--bindmounts the entire subtree of the target path. - Implicit Traversal:
bwrapautomatically creates the parent directories of any--bindor--ro-binddestination as empty directories. This allows a path likeRW /home/user/project/srcto be reachable inside the namespace even if its parent directories are not explicitly bound, without exposing the host's actual parent content. - Path Resolution: Path resolution follows a "most-specific-path-wins" rule (longest-prefix) via the
filesystem_resolve.rsresolver inwxc_common.
- Default-deny: If no
Understand ContainerConfig and backend-specific settings
mainA
ContainerConfigis the complete configuration for a specific backend. It is generated viacreateConfigFromPolicy()and can be modified by the user before being passed tospawnSandboxFromConfig().Key Rules:
- One backend per Config: A configuration is exclusive to one backend. For example, a Windows process config contains a
processcontainersection but nolxcsection. A Linux process config contains anlxcsection but noprocesscontainersection. - User-modifiable: Advanced users can override any field in the
ContainerConfigbefore spawning. - Schema-defined: Configs follow strict schemas (found in
schemas/) and are mirrored by SDK TypeScript types.
All configs share common sections like
filesystem,network, andui. Backend-specific fields are scoped to their respective sections (e.g.,processcontainer.uifor Windows process containment).type ContainerConfig = | ProcessContainerConfig | LxcContainerConfig | MicroVmConfig;- One backend per Config: A configuration is exclusive to one backend. For example, a Windows process config contains a
Determine where to add a new feature
mainBefore implementing a feature, use the decision logic to identify which components require updates.
- Cross-platform security restrictions: Requires updates to
SandboxPolicy, theContainerConfigschema, the TypeScript SDK (@microsoft/mxc-sdk), and the Rust executors. - Backend-specific configuration: Requires updates to the
ContainerConfigschema, adding a containment type tocreateConfigFromPolicy, updating SDK defaults, and updating executors. - SDK or Executor only: If the feature does not affect security policy or cross-platform configuration, it may only require changes to the TypeScript SDK library or the Rust executors.
Note: Any change to the Config schema always requires changes to the TypeScript SDK library (
@microsoft/mxc-sdk) because the SDK generates the Config.- Cross-platform security restrictions: Requires updates to
Understand version coupling for Windows.AI.IsolationSession bindings
mainThe Rust bindings located in
src/backends/isolation_session/bindings/are generated from a WinMD file. These bindings are strictly coupled to a specific version of thewindowscrate.If the project's
windowscrate version is upgraded, the bindings crate'sbuild.rswill fail. When this happens, you must regenerate the bindings to match the new crate version. The specific required version for thewindowscrate is defined inGENERATION_INFO.tomlunder thetarget_windows_cratekey.Understand the MXC Network Configuration GA schema
mainThe MXC Network Configuration is transitioning to a new GA (General Availability) schema. This schema replaces the legacy
allowedHosts/blockedHosts/defaultPolicyformat with a more structured approach definingegress,ingress, andproxysettings.- Egress: Defines how traffic leaves the container. It uses a
defaultpolicy (e.g.,deny), anallowlist containing destination CIDRs and specific protocol/port combinations, and adenylist for explicit blocks. - Ingress: Controls incoming traffic; for example, setting
hostLoopbacktodenyprevents the container from accessing the host's loopback interface. - Proxy: Configures HTTP proxy settings (e.g.,
httpkey with a host/port string).
{ "network": { "egress": { "default": "deny", "allow": [{ "to": [{ "cidr": "140.82.112.0/20" }], "ports": [{ "protocol": "tcp", "port": 443 }] }], "deny": [{ "to": [{ "cidr": "10.0.0.0/8" }] }] }, "ingress": { "hostLoopback": "deny" }, "proxy": { "http": "127.0.0.1:8080" } } }- Egress: Defines how traffic leaves the container. It uses a
Configure Process Environment and Working Directory in LXC
mainThe LXC backend implements standard
process.cwdandprocess.envfields:Working Directory (
process.cwd)Implemented via a
cd -- "$1" && exec /bin/sh -c "$2"wrapper.- An empty string preserves the container's default CWD.
- Paths with spaces, quotes,
$vars, or backticks are passed verbatim via positional arguments to avoid shell escaping issues. - Non-existent or unpermitted paths result in a non-zero exit code (typically
1).
Environment Variables (
process.env)- Each
KEY=VALentry is passed via the--set-var=KEY=VALflag tolxc-attach. - Replace Semantics: If
process.envis non-empty,lxc-execuses--clear-envto prevent host environment leakage. Theprocess.envvalues take precedence over the host. - Malformed Entries: Entries without
=or with an empty key (e.g.,"=foo") are silently skipped. - Baseline: Even with
--clear-env, a small baseline (container,HOME,TERM, a defaultPATH,USER) is injected bylxc-attach.
Configure Filesystem and Network Policies for LXC
mainFilesystem Policy
Enforced via bind mounts:
readwritePaths: Mounted asbind,rw. The script can read and write.readonlyPaths: Mounted asbind,ro. The script can read but not write.deniedPaths: No mount ortmpfsoverlay is used; the path is inaccessible.
Network Policy
Enforced via
iptables/nftablesrules on the container's virtual ethernet (veth) interface:defaultPolicy: "block": DefaultDROPrule.defaultPolicy: "allow": DefaultACCEPTrule.allowedHosts:ACCEPTrules for specific IPs/CIDRs.blockedHosts:DROPrules for specific IPs/CIDRs.
Important Notes:
- IPv4 Only: Firewall mode only resolves
allowedHostsandblockedHoststo IPv4 addresses. IPv6 (AAAA) records and literals are silently dropped. - Cleanup: Rules are automatically cleaned up when the container exits if
removeRulesOnExitis set totrue.
Configure outbound network access (Egress)
mainMXC uses a default-deny outbound policy. To allow network access, you must explicitly list destinations in the configuration.
Key Rules:
- Default Deny: Unlisted destinations are unreachable. A configuration that mentions nothing grants nothing.
- Precedence: If a connection matches both an
egress.allowrule and anegress.denyrule, the deny rule wins. - Address Types: You must use IPv4/IPv6 literals or CIDRs. DNS names are rejected at validation time and cannot be used in rules.
- Default Behavior:
- With
egress.default: "deny", no matching allow means no outbound access. - With
egress.default: "allow", no matching deny means unrestricted outbound.
- With
Platform Enforcement:
- Windows: Uses WFP (Windows Filtering Platform) on process containers.
- Linux: Uses a network namespace and
iptableson WSLc, LXC, or Bubblewrap backends. - macOS: Uses a Seatbelt profile that restricts outbound traffic to the loopback proxy port.
Understand the WSLC two-step lifecycle
mainRunning Linux containers from Windows via MXC follows a two-step lifecycle pattern to optimize performance and execution:
- Pre-pull (One-time per image): Pull the required container image into the SDK cache. This step ensures the image is available locally before execution.
- Execute (Any number of times): Run the execution task against the already cached image. This allows for rapid, repeated execution without the overhead of network pulls.
Understand inter-container networking constraints
mainInter-container communication depends heavily on the backend being used:
- Windows process containers: Supports communication between two AppContainers over host loopback, provided that directional AppContainer loopback-exemption rules are installed for the pair.
- WSLc / LXC / Bubblewrap: These backends use private network namespaces.
127.0.0.1is local to the sandbox, meaning sandboxes cannot reach each other via loopback. Inter-container communication requires explicit virtual networking (e.g., veth, bridges, or shared namespaces), which is currently out of scope for GA. - macOS (Seatbelt): Seatbelt does not use network namespaces. Processes in different Seatbelt sandboxes share the host loopback and can communicate via
127.0.0.1or Unix sockets/XPC if their profiles allow it. This is considered host-level IPC rather than isolated container networking.