MXC macOS GUI Access Options with Seatbelt Backend: Configuration Guide
MXC enables macOS GUI access through the Seatbelt backend by setting experimental.seatbelt.guiAccess to true, which injects Mach service and IOKit rules into a dynamically generated sandbox profile.
The microsoft/mxc repository provides a containment runtime that uses Apple's Seatbelt sandbox to isolate processes on macOS. By default, this sandbox blocks all UI-related services, preventing graphical applications from interacting with WindowServer. MXC exposes experimental configuration options that allow developers to selectively enable GUI access while maintaining process isolation.
Understanding the Seatbelt Sandbox Constraints
By default, the Seatbelt backend denies access to UI-related services, which prevents sandboxed processes from opening windows or interacting with the macOS WindowServer. This isolation is intentional for security, but it prevents running graphical applications like Terminal.app or Calculator within the sandbox.
To address this, MXC provides experimental options under the experimental.seatbelt configuration namespace. These options modify the TinyScheme profile generated by the backend to allow specific Mach service lookups and IOKit access required for GUI rendering.
Experimental Configuration Options
The Seatbelt backend reads four key experimental fields from the JSON configuration, parsed in src/core/wxc_common/src/config_parser.rs, to determine GUI capabilities:
experimental.seatbelt.guiAccess(boolean, default:false): When enabled, the profile builder adds wildcardmach-lookupandiokit-openrules to communicate withcom.apple.windowserver, pasteboard services, and HID devices.experimental.seatbelt.launchMethod("exec"or"open", default:"exec"): Determines process spawning strategy."exec"runs directly aftersandbox_init(), while"open"spawns Terminal.app via LaunchServices to satisfy Launch Constraints.experimental.seatbelt.nestedPty(boolean, default:true): Grants(allow pseudo-pty)and access to/dev/ptmx, preserving terminal allocation capabilities for REPLs and interactive tools.experimental.seatbelt.keychainAccess(boolean, default:false): Opens additional Mach services and file paths for macOS Keychain access, disabled by default for GUI workloads.
These fields are documented in the Seatbelt backend guide at docs/macos-support/seatbelt-backend.md and processed by the profile builder in src/backends/seatbelt/common/src/profile_builder.rs.
How GUI Access Works Under the Hood
When guiAccess is set to true, the profile builder injects specific allow rules into the generated TinyScheme profile. According to the implementation in src/backends/seatbelt/common/src/profile_builder.rs, the backend inserts the following rules after the default filesystem and network restrictions:
(allow mach-lookup (global-name "com.apple.windowserver"))
(allow mach-lookup (global-name "com.apple.pasteboard.1"))
(allow iokit-open (iokit-user-client-class "IOHIDLibUserClient"))
These rules enable the sandboxed process to communicate with WindowServer for window rendering, access the system pasteboard for clipboard operations, and interact with HID devices for input handling. The profile generation ensures UI permissions are granted only when explicitly requested through the experimental configuration.
Launch Method Strategies
The launchMethod parameter controls how the sandboxed process initializes, handled by src/backends/seatbelt/common/src/seatbelt_runner.rs:
exec (default): The backend calls sandbox_init() in the child's pre_exec hook, then executes the target command directly. This method works for most native AppKit applications that do not depend on a specific parent terminal relationship.
open: Required specifically for Terminal.app, which enforces Launch Constraints that terminate child processes started via unauthorized parents. This method spawns Terminal.app via macOS LaunchServices (open -n -W -a Terminal …) and then applies the sandbox to the inner shell using the sandbox-exec CLI.
Practical Configuration Examples
Enabling GUI Access for Native Apps
To run a native AppKit application like Calculator with GUI support, use the exec launch method:
{
"$schema": "./schemas/dev/mxc-config.schema.0.7.0-dev.json",
"containment": "seatbelt",
"process": {
"commandLine": "open -a Calculator"
},
"experimental": {
"seatbelt": {
"guiAccess": true,
"launchMethod": "exec"
}
}
}
Execute with the experimental flag:
./mxc-exec-mac --experimental config.json
Sandboxing Terminal.app
Terminal requires the open launch method to satisfy its Launch Constraints:
{
"containment": "seatbelt",
"process": {
"commandLine": "bash -c 'echo hello && sleep 5'"
},
"experimental": {
"seatbelt": {
"guiAccess": true,
"launchMethod": "open"
}
}
}
This configuration launches Terminal.app, applies the sandbox to the inner shell, and executes the command while preserving window functionality.
SDK Implementation
For programmatic access using the TypeScript SDK:
import { spawnSandbox, SandboxPolicy } from '@microsoft/mxc-sdk';
const policy: SandboxPolicy = {
process: { commandLine: 'open -a Calculator' },
experimental: {
seatbelt: {
guiAccess: true,
launchMethod: 'exec',
},
},
};
const pty = spawnSandbox('', policy, { experimental: true });
pty.onData(d => console.log(d));
pty.onExit(e => console.log('Exited', e));
The SDK automatically selects the mxc-exec-mac binary and passes the Seatbelt configuration through the experimental flag.
Debugging Generated Profiles
To inspect the generated TinyScheme profile without executing:
./mxc-exec-mac --experimental --debug --dry-run config.json
The --dry-run flag outputs the complete profile to stdout, showing the UI rules added when guiAccess: true is configured.
Limitations and Edge Cases
GUI support in the Seatbelt backend carries specific constraints that developers must consider:
- Native AppKit Only: GUI support is limited to native AppKit applications. Electron-based apps often relaunch via helper processes that escape the sandbox profile.
- Terminal.app Exclusivity: Only Terminal.app can be sandboxed with
launchMethod: "open". Other system applications like Calculator or TextEdit lack an inner shell and are killed by Launch Constraints when spawned through this method. - Process Scope: The sandbox remains process-scoped, not a persistent container. Each invocation creates a fresh sandboxed process tree with no state persistence between runs.
Key Source Files and Implementation Details
The following files drive GUI access behavior in the microsoft/mxc repository:
| File | Role |
|---|---|
docs/macos-support/seatbelt-backend.md |
Authoritative documentation for Seatbelt backend configuration options |
src/backends/seatbelt/common/src/profile_builder.rs |
Generates TinyScheme profiles and adds UI, PTY, and Keychain rules |
src/backends/seatbelt/common/src/seatbelt_runner.rs |
Executes sandboxed processes and handles launchMethod logic |
src/core/wxc_common/src/models.rs |
Defines the ContainmentBackend::Seatbelt enum and configuration structures |
src/core/wxc_common/src/config_parser.rs |
Parses JSON configuration and populates SeatbeltConfig with defaults |
Summary
- MXC enables macOS GUI access through experimental Seatbelt backend options that modify dynamically generated sandbox profiles.
- Set
experimental.seatbelt.guiAccesstotrueto inject WindowServer, pasteboard, and HID access rules into the TinyScheme profile. - Use
launchMethod: "open"specifically for Terminal.app to satisfy Launch Constraints, while"exec"works for most other native AppKit applications. - Inspect generated profiles using the
--dry-runflag to verify Mach service and IOKit permissions before execution. - Electron applications and persistent container semantics are not supported by the current Seatbelt backend implementation.
Frequently Asked Questions
How do I enable GUI access for native AppKit apps in MXC Seatbelt?
Set experimental.seatbelt.guiAccess to true in your JSON configuration and use "launchMethod": "exec". This instructs the profile builder in src/backends/seatbelt/common/src/profile_builder.rs to add mach-lookup rules for WindowServer and iokit-open permissions for HID devices, allowing the sandboxed process to create windows and receive input.
Why does Terminal.app require the "open" launch method instead of "exec"?
Terminal.app enforces Launch Constraints that terminate child processes started via unauthorized parents. The "open" method spawns Terminal via macOS LaunchServices (open -n -W -a Terminal …) and then applies the sandbox to the inner shell using sandbox-exec, satisfying these constraints. This logic is implemented in src/backends/seatbelt/common/src/seatbelt_runner.rs.
Can I use MXC Seatbelt backend with Electron-based applications?
No. Electron apps typically relaunch through helper processes that escape the process-scoped sandbox. The Seatbelt backend currently supports only native AppKit applications that run within a single process tree.
How can I inspect the generated Seatbelt profile before execution?
Run the MXC binary with the --dry-run flag combined with --experimental and --debug. This outputs the generated TinyScheme profile to stdout, allowing you to verify that guiAccess: true has correctly inserted the WindowServer and pasteboard allow rules before risking execution failures.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →