# How the Seatbelt Backend Generates TinyScheme Profiles in MXC

> Discover how the Seatbelt backend generates TinyScheme profiles dynamically using the build_profile function in MXC. Learn about layered rules and Apple's sandbox_init API.

- Repository: [Microsoft/mxc](https://github.com/microsoft/mxc)
- Tags: how-to-guide
- Published: 2026-06-07

---

**The Seatbelt backend translates MXC ExecutionRequest policies into TinyScheme sandbox profiles at runtime through the `build_profile` function, assembling layered allow/deny rules before applying them via Apple's `sandbox_init` API.**

The Seatbelt backend in the Microsoft MXC (Multi-platform eXecution Container) project provides macOS sandboxing by generating TinyScheme profiles that Apple's Seatbelt security framework can enforce. When you specify `containment: "seatbelt"` in an execution request, the backend dynamically constructs a sandbox profile string that translates your MXC policy into native macOS sandbox rules. This process involves translating filesystem, network, and execution constraints into Scheme expressions that the `sandbox_init` system call consumes.

## Profile Construction in `build_profile`

The core generation logic resides in [`src/backends/seatbelt/common/src/profile_builder.rs`](https://github.com/microsoft/mxc/blob/main/src/backends/seatbelt/common/src/profile_builder.rs), specifically within the `build_profile` function. This pure-Rust builder transforms an `ExecutionRequest` into a valid TinyScheme string through a structured assembly process.

### Profile Override Handling

Before generating rules, the function checks for `experimental.seatbelt.profile_override`. If present, the builder returns this raw TinyScheme string verbatim, bypassing all automatic rule generation.

### Layered Rule Assembly

When no override exists, the builder constructs the profile in strict order:

1. **Header and Baseline** – `(version 1)` and `(deny default)` establish the deny-by-default security posture
2. **Baseline Allow Rules** – `BASELINE_ALLOW` constants permitting essential process operations
3. **System Read-Only** – `SYSTEM_READ_ALLOW` for necessary system library access
4. **PTY Access** – `TTY_ALLOW` rules for terminal interaction
5. **Policy-Derived Allows** – Dynamic rules for:
   - Filesystem access (`write_filesystem_allow`)
   - Network connectivity (`write_network_rules`)
   - Nested PTY and Keychain access
   - UI isolation when `guiAccess` is enabled
6. **Policy-Derived Denies** – Deny rules for `deniedPaths` via `write_filesystem_deny`

The builder uses `expand_tilde` to resolve home directory paths and `quote_scheme` to escape strings for Scheme literal safety.

## Runner Invocation and Sandbox Application

Once constructed, the profile string passes to `SeatbeltScriptRunner` in [`src/backends/seatbelt/common/src/seatbelt_runner.rs`](https://github.com/microsoft/mxc/blob/main/src/backends/seatbelt/common/src/seatbelt_runner.rs). This component handles the actual sandbox instantiation through two distinct launch methods.

### Exec Mode

In `exec` mode (the default), the runner forks a child process and uses `Command::pre_exec` to invoke `sandbox_init` with the generated profile before executing `/bin/sh -c <script>`. This applies the sandbox restrictions immediately before the target script gains control.

### Open Mode

When `experimental.seatbelt.launch_method` is set to `"open"`, the runner instead uses LaunchServices to spawn a helper application (such as Terminal.app) and applies the profile using the `sandbox-exec` CLI utility, enabling GUI applications to run within the sandbox.

## Rule Ordering and Last-Match-Wins Semantics

The generated profile follows Apple's **last-match-wins** evaluation order. By emitting broad allow rules first and specific deny rules for `deniedPaths` last, the backend ensures that explicit denials override general allowances. This ordering matches MXC's semantic model across all containment backends, allowing `deniedPaths` to prevail even when parent directories appear in `readwritePaths`.

## Practical Examples

### Generating Profiles Directly in Rust

```rust
use wxc_common::models::{ExecutionRequest, Policy, NetworkPolicy};
use seatbelt_common::profile_builder::build_profile;

let request = ExecutionRequest {
    policy: Policy {
        readonly_paths: vec!["/Users/me/project".into()],
        readwrite_paths: vec!["/tmp/output".into()],
        denied_paths: vec!["/Users/me/.ssh".into()],
        default_network_policy: NetworkPolicy::Block,
        allowed_hosts: vec![],
        blocked_hosts: vec![],
        ..Default::default()
    },
    experimental: Default::default(),
    script_code: "echo hello from seatbelt".into(),
    ..Default::default()
};

let profile = build_profile(&request).expect("profile generation");
println!("{}", profile);

```

### Using the TypeScript SDK

```typescript
import { exec } from "@microsoft/mxc";

await exec({
  containment: "seatbelt",
  process: { commandLine: "echo hi from seatbelt", timeout: 30000 },
  filesystem: {
    readwritePaths: ["/tmp/out"],
    readonlyPaths: ["/Users/me/project"],
    deniedPaths: ["/Users/me/.ssh"]
  },
  network: { defaultPolicy: "block" },
  experimental: {
    seatbelt: {
      launchMethod: "exec",
      guiAccess: false
    }
  }
});

```

### Supplying a Raw Profile Override

```json
{
  "experimental": {
    "seatbelt": {
      "profileOverride": "(version 1)\n(deny default)\n(allow file-read-data (literal \"/usr/bin\"))\n(deny file-read* file-write* (subpath \"/secret\"))"
    }
  }
}

```

When provided, this TinyScheme string bypasses the automatic rule builder entirely.

## Summary

- The `build_profile` function in [`profile_builder.rs`](https://github.com/microsoft/mxc/blob/main/profile_builder.rs) constructs TinyScheme profiles by layering allow rules atop a deny-by-default baseline, finishing with specific deny rules for `deniedPaths`
- Helper functions `expand_tilde` and `quote_scheme` ensure path literals are correctly escaped for Scheme syntax
- `SeatbeltScriptRunner` applies profiles via `sandbox_init` in `exec` mode or through `sandbox-exec` in `open` mode
- Rule ordering follows Apple's last-match-wins semantics, allowing explicit denials to override broader allowances
- Users can bypass automatic generation entirely using `experimental.seatbelt.profileOverride` in the ExecutionRequest

## Frequently Asked Questions

### What is the difference between `exec` and `open` launch methods in the Seatbelt backend?

The `exec` method forks a new process, applies the TinyScheme profile via the `sandbox_init` C API using `Command::pre_exec`, and then executes the target script through `/bin/sh`. The `open` method instead uses macOS LaunchServices to spawn a helper application like Terminal.app, applying the sandbox through the `sandbox-exec` CLI tool. Use `exec` for command-line tools and `open` for GUI applications requiring WindowServer access.

### How does the Seatbelt backend handle path escaping in TinyScheme profiles?

The builder uses the `quote_scheme` helper function to escape user-provided paths into valid Scheme string literals. This prevents injection attacks and syntax errors when paths contain spaces, quotes, or other special characters. Additionally, `expand_tilde` resolves `~` characters to absolute home directory paths before quoting occurs.

### Can I use custom TinyScheme profiles with the MXC Seatbelt backend?

Yes. Set `experimental.seatbelt.profileOverride` to a raw TinyScheme string in your ExecutionRequest. When present, the `build_profile` function returns this string verbatim without generating baseline, filesystem, or network rules, giving you complete control over the sandbox policy while still using MXC's execution infrastructure.

### Why are deny rules emitted last in the generated TinyScheme profile?

Apple's Seatbelt evaluator uses last-match-wins semantics, meaning the final matching rule in the profile determines access. By emitting `deniedPaths` rules after broader allow rules, the backend ensures that explicit denials take precedence over general filesystem allowances, maintaining consistency with MXC's security model across all platforms.