From 54b417e5310bfcfdb7fdfc1507a2f828d2ff8bd1 Mon Sep 17 00:00:00 2001 From: Richie Gomez Date: Fri, 5 Jun 2026 09:44:26 -0700 Subject: [PATCH 1/2] Promote seatbelt backend from experimental to stable Remove the --experimental flag requirement for macOS seatbelt invocations. The binary still accepts the flag (no-op) for backward compatibility with older SDK versions and configs. Changes: - Remove seatbelt from ExperimentalBackends list in SDK types - Remove experimental gate in mxc_darwin/main.rs - Update unit tests to reflect seatbelt no longer requires experimental - Update seatbelt-backend.md CLI examples (drop --experimental) - Update 0.7.0-dev schema descriptions - Update copilot-instructions.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 2 +- docs/macos-support/seatbelt-backend.md | 20 +++++++------------- schemas/dev/mxc-config.schema.0.7.0-dev.json | 4 ++-- sdk/src/types.ts | 4 ++-- sdk/tests/unit/sandbox.test.ts | 17 ++--------------- src/core/mxc_darwin/src/main.rs | 9 --------- 6 files changed, 14 insertions(+), 42 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2bb88ee2..c1f8f934 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -152,7 +152,7 @@ Per-backend guides: - `docs/base-process-container/guide.md` — process container (Windows AppContainer / BaseContainer) - `docs/base-process-container/UIPolicy_Schema.md` — UI policy schema (JOB_OBJECT_UILIMIT_* mappings) - `docs/lxc-support/lxc-backend.md` — LXC container backend (Linux) -- `docs/macos-support/seatbelt-backend.md` — macOS Seatbelt backend (experimental) +- `docs/macos-support/seatbelt-backend.md` — macOS Seatbelt backend - `docs/windows-sandbox/windows-sandbox.md` / `docs/windows-sandbox/windows-sandbox-reference.md` — Windows Sandbox backend - `docs/wsl/wsl-container-getting-started.md` / `docs/wsl/wsl-container-support-plan.md` — WSL Container (WSLC SDK) - `docs/nanvix-microvm/nanvix.md` / `docs/nanvix-microvm/nanvix-integration-plan.md` — MicroVM via NanVix diff --git a/docs/macos-support/seatbelt-backend.md b/docs/macos-support/seatbelt-backend.md index 237ebb70..249e8d71 100644 --- a/docs/macos-support/seatbelt-backend.md +++ b/docs/macos-support/seatbelt-backend.md @@ -132,8 +132,7 @@ You should see sandbox profile generation output followed by The macOS sandbox backend uses the same JSON configuration schema as the other backends, with `containment` set to `"seatbelt"`. Backend-specific -settings live under `experimental.seatbelt`, and the `--experimental` -flag is required to enable the backend at runtime: +settings live under `experimental.seatbelt`: ```json { @@ -214,22 +213,18 @@ SDK rejects it with a clear error, mirroring the Linux behavior. ### Command line -The `seatbelt` backend is currently experimental, so every invocation -must include the `--experimental` flag. Without it, the binary refuses to -run with a clear error. - ```bash # Run with config file -./mxc-exec-mac --experimental config.json +./mxc-exec-mac config.json # Run with base64-encoded config -./mxc-exec-mac --experimental --config-base64 +./mxc-exec-mac --config-base64 # Validate the config and exit without executing -./mxc-exec-mac --experimental --dry-run config.json +./mxc-exec-mac --dry-run config.json # Diagnostic output to console + file -./mxc-exec-mac --experimental --debug --log-file mxc.log config.json +./mxc-exec-mac --debug --log-file mxc.log config.json ``` ### SDK @@ -248,9 +243,8 @@ const policy: SandboxPolicy = { }; // On macOS, spawnSandbox automatically resolves to mxc-exec-mac and -// builds a seatbelt config. The backend is experimental, so the -// caller must opt in via SandboxSpawnOptions.experimental. -const pty = spawnSandbox('echo hello', policy, { experimental: true }); +// builds a seatbelt config. +const pty = spawnSandbox('echo hello', policy); pty.onData((data) => console.log(data)); pty.onExit((e) => console.log('Exit:', e.exitCode)); ``` diff --git a/schemas/dev/mxc-config.schema.0.7.0-dev.json b/schemas/dev/mxc-config.schema.0.7.0-dev.json index f757c227..b1794538 100644 --- a/schemas/dev/mxc-config.schema.0.7.0-dev.json +++ b/schemas/dev/mxc-config.schema.0.7.0-dev.json @@ -31,7 +31,7 @@ "bubblewrap" ], "default": "process", - "description": "Containment value to use for execution. Accepts both abstract intents ('process', 'vm') and concrete backends ('processcontainer', 'windows_sandbox', 'lxc', 'microvm', 'hyperlight', 'wslc', 'seatbelt', 'isolation_session', 'bubblewrap'). The native binary resolves abstract intents to a concrete backend at run time based on host capabilities (e.g., 'process' resolves to ProcessContainer on Windows, Bubblewrap on Linux, Seatbelt on macOS; 'vm' resolves to Windows Sandbox on Windows). 'lxc' is treated as a full Linux container and is only selected when explicitly requested. Note: 'microvm', 'windows_sandbox', 'wslc', 'seatbelt', 'isolation_session', and 'hyperlight' are experimental and require the --experimental CLI flag. 'hyperlight' and 'bubblewrap' have no per-backend configuration block; they share the common filesystem/network policy fields. The legacy aliases 'appcontainer' and 'macos_sandbox' are still accepted by the parser (with a deprecation log line) but are intentionally omitted from this enum so editors steer authors toward the canonical names." + "description": "Containment value to use for execution. Accepts both abstract intents ('process', 'vm') and concrete backends ('processcontainer', 'windows_sandbox', 'lxc', 'microvm', 'hyperlight', 'wslc', 'seatbelt', 'isolation_session', 'bubblewrap'). The native binary resolves abstract intents to a concrete backend at run time based on host capabilities (e.g., 'process' resolves to ProcessContainer on Windows, Bubblewrap on Linux, Seatbelt on macOS; 'vm' resolves to Windows Sandbox on Windows). 'lxc' is treated as a full Linux container and is only selected when explicitly requested. Note: 'microvm', 'windows_sandbox', 'wslc', 'isolation_session', and 'hyperlight' are experimental and require the --experimental CLI flag. 'hyperlight' and 'bubblewrap' have no per-backend configuration block; they share the common filesystem/network policy fields. The legacy aliases 'appcontainer' and 'macos_sandbox' are still accepted by the parser (with a deprecation log line) but are intentionally omitted from this enum so editors steer authors toward the canonical names." }, "phase": { @@ -387,7 +387,7 @@ }, "seatbelt": { "type": "object", - "description": "macOS sandbox backend (experimental). Used when containment is 'seatbelt'.", + "description": "macOS sandbox backend configuration. Used when containment is 'seatbelt'.", "properties": { "profileOverride": { "type": "string", diff --git a/sdk/src/types.ts b/sdk/src/types.ts index 98c94961..1b1a6f06 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -107,7 +107,7 @@ export type ContainmentBackend = * Containment values (abstract intent or concrete backend) that require * the `--experimental` flag. */ -export const ExperimentalBackends: readonly (ContainmentType | ContainmentBackend)[] = ['microvm', 'windows_sandbox', 'hyperlight', 'wslc', 'seatbelt', 'isolation_session']; +export const ExperimentalBackends: readonly (ContainmentType | ContainmentBackend)[] = ['microvm', 'windows_sandbox', 'hyperlight', 'wslc', 'isolation_session']; /** * Clipboard access policy levels @@ -348,7 +348,7 @@ export interface LxcConfig { } /** - * macOS Seatbelt sandbox configuration (experimental). Used under + * macOS Seatbelt sandbox configuration. Used under * `experimental.seatbelt` when containment is `'seatbelt'`. */ export interface SeatbeltConfig { diff --git a/sdk/tests/unit/sandbox.test.ts b/sdk/tests/unit/sandbox.test.ts index 0c96a251..03a39986 100644 --- a/sdk/tests/unit/sandbox.test.ts +++ b/sdk/tests/unit/sandbox.test.ts @@ -1111,26 +1111,13 @@ describe('resolveExecutableAndArgs (containment validation)', { skip: platformSk ); }); - it('should require experimental mode for "macos_sandbox" (mirrors seatbelt gating)', () => { - // Regression: pre-fix, ExperimentalBackends.includes('macos_sandbox') was - // false, so the legacy alias bypassed the experimental gate entirely. - // The gate must look at the resolved backend, not the raw wire value. - assert.throws( - () => resolveExecutableAndArgs(makeConfig('macos_sandbox'), { executablePath: fakeExe }), - { message: /experimental mode/ }, - ); - }); - - it('should accept "macos_sandbox" on macOS with the experimental flag set', function (this: { skip: (reason?: string) => void }) { + it('should accept "macos_sandbox" on macOS', function (this: { skip: (reason?: string) => void }) { if (process.platform !== 'darwin') { this.skip('seatbelt is macOS-only'); return; } assert.doesNotThrow(() => - resolveExecutableAndArgs(makeConfig('macos_sandbox'), { - executablePath: fakeExe, - experimental: true, - }), + resolveExecutableAndArgs(makeConfig('macos_sandbox'), { executablePath: fakeExe }), ); }); diff --git a/src/core/mxc_darwin/src/main.rs b/src/core/mxc_darwin/src/main.rs index b0dea97a..b862a75b 100644 --- a/src/core/mxc_darwin/src/main.rs +++ b/src/core/mxc_darwin/src/main.rs @@ -113,15 +113,6 @@ fn main() { log_request(&request, &mut logger); - // The macOS sandbox backend is experimental — require the flag. - if !request.experimental_enabled { - eprintln!( - "Error: the macOS sandbox backend is experimental. \ - Pass --experimental to enable it." - ); - process::exit(1); - } - // The SDK should always select Seatbelt on darwin. Be lenient and // log a note instead of failing — same behaviour as `lxc-exec`. if request.containment != ContainmentBackend::Seatbelt { From 0c97ff1e9f308f9cceeff546b52304eff41e7d40 Mon Sep 17 00:00:00 2001 From: Richie Gomez Date: Fri, 5 Jun 2026 09:56:52 -0700 Subject: [PATCH 2/2] Add top-level seatbelt config key (two-phase migration) Support 'seatbelt' as a top-level config key alongside the deprecated 'experimental.seatbelt' path. The parser prefers the top-level key when both are present. Old configs using experimental.seatbelt continue to work unchanged. Changes: - Rust config_parser: add top-level 'seatbelt' field to RawConfig, merge logic that prefers top-level over experimental.seatbelt - Rust models: update section_path() to return 'seatbelt', update docs - SDK types.ts: add top-level seatbelt field to ContainerConfig, mark experimental.seatbelt as deprecated - SDK sandbox.ts: buildDarwinProcessConfig writes to config.seatbelt - Add tests for top-level path and precedence Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/macos-support/seatbelt-backend.md | 20 ++++---- sdk/src/sandbox.ts | 9 ++-- sdk/src/types.ts | 13 +++-- src/core/wxc_common/src/config_parser.rs | 61 +++++++++++++++++++++++- src/core/wxc_common/src/models.rs | 9 ++-- 5 files changed, 87 insertions(+), 25 deletions(-) diff --git a/docs/macos-support/seatbelt-backend.md b/docs/macos-support/seatbelt-backend.md index 249e8d71..813aa582 100644 --- a/docs/macos-support/seatbelt-backend.md +++ b/docs/macos-support/seatbelt-backend.md @@ -132,7 +132,9 @@ You should see sandbox profile generation output followed by The macOS sandbox backend uses the same JSON configuration schema as the other backends, with `containment` set to `"seatbelt"`. Backend-specific -settings live under `experimental.seatbelt`: +settings live under a top-level `seatbelt` key (preferred) or under +`experimental.seatbelt` (deprecated, still accepted for backward +compatibility): ```json { @@ -151,10 +153,8 @@ settings live under `experimental.seatbelt`: "defaultPolicy": "block", "allowedHosts": ["api.github.com"] }, - "experimental": { - "seatbelt": { - "mode": "exec" - } + "seatbelt": { + "nestedPty": true } } ``` @@ -163,11 +163,11 @@ settings live under `experimental.seatbelt`: | Field | Type | Default | Description | |---|---|---|---| -| `experimental.seatbelt.profileOverride` | string | unset | Optional override of the generated TinyScheme sandbox profile. When set, the SDK-generated profile is replaced with this raw TinyScheme string verbatim — all `filesystem`/`network`/`ui` policy fields are ignored for profile generation (they are still type-checked). Use this only when the auto-generated profile is insufficient. | -| `experimental.seatbelt.guiAccess` | boolean | `false` | When `true`, adds wildcard Mach service and IOKit rules so GUI applications can create windows and render via WindowServer. Requires `ui.disable: false`. Native AppKit apps (e.g. Terminal.app) work well; Electron-based apps may escape the sandbox via re-launch patterns. | -| `experimental.seatbelt.launchMethod` | `"exec"` \| `"open"` | `"exec"` | How to launch the sandboxed process. `"exec"` (default) uses the `sandbox_init()` API in `pre_exec` then execs the command directly — works for third-party GUI apps (Alacritty, etc.) and all CLI commands. `"open"` launches Terminal.app via LaunchServices (`open -n -W -a Terminal`) then applies the sandbox to the inner shell via the `sandbox-exec` CLI tool. This is required because Terminal.app enforces Apple Launch Constraints that kill it when exec'd by unauthorized parents. Currently only Terminal.app is supported with the `"open"` method — other Apple system apps (Calculator, TextEdit) cannot be sandboxed due to Launch Constraints and lack of an inner shell to constrain. | -| `experimental.seatbelt.nestedPty` | boolean | `true` | When `true`, the inner process can allocate its own pseudo-terminals via `posix_openpt`. Required by anything that spawns a shell (test runners, `git`, `gh`, REPLs, agent tools that wrap commands in a pty). Adds `(allow pseudo-tty)` and read/write/ioctl on `/dev/ptmx` to the generated profile. Set to `false` for a tighter sandbox when the inner command does not need to allocate new ttys. | -| `experimental.seatbelt.keychainAccess` | boolean | `false` | When `true`, opens the sandbox enough for `keytar` / `Security.framework` to reach the macOS Keychain end-to-end. Adds Mach lookup for `com.apple.SecurityServer`, `com.apple.securityd`, `com.apple.trustd`, `com.apple.ocspd`, `com.apple.cfprefsd.daemon`, `com.apple.xpcd`, and the `com.apple.lsd.*` family (regex); read access to `/private/var/db/mds` (Spotlight/MDS metadata) and `/private/var/protected/trustd` (trustd protected store); and read+write access to `~/Library/Keychains` (user keychain DB) and `/private/var/folders` (XPC cache and per-user containers). The system keychain stores under `/Library/Keychains` and `/System/Library/Keychains` are already covered by the baseline `/Library` and `/System` read-only allows. Off by default — opt in only when the inner workload genuinely needs Keychain access. | +| `seatbelt.profileOverride` | string | unset | Optional override of the generated TinyScheme sandbox profile. When set, the SDK-generated profile is replaced with this raw TinyScheme string verbatim — all `filesystem`/`network`/`ui` policy fields are ignored for profile generation (they are still type-checked). Use this only when the auto-generated profile is insufficient. | +| `seatbelt.guiAccess` | boolean | `false` | When `true`, adds wildcard Mach service and IOKit rules so GUI applications can create windows and render via WindowServer. Requires `ui.disable: false`. Native AppKit apps (e.g. Terminal.app) work well; Electron-based apps may escape the sandbox via re-launch patterns. | +| `seatbelt.launchMethod` | `"exec"` \| `"open"` | `"exec"` | How to launch the sandboxed process. `"exec"` (default) uses the `sandbox_init()` API in `pre_exec` then execs the command directly — works for third-party GUI apps (Alacritty, etc.) and all CLI commands. `"open"` launches Terminal.app via LaunchServices (`open -n -W -a Terminal`) then applies the sandbox to the inner shell via the `sandbox-exec` CLI tool. This is required because Terminal.app enforces Apple Launch Constraints that kill it when exec'd by unauthorized parents. Currently only Terminal.app is supported with the `"open"` method — other Apple system apps (Calculator, TextEdit) cannot be sandboxed due to Launch Constraints and lack of an inner shell to constrain. | +| `seatbelt.nestedPty` | boolean | `true` | When `true`, the inner process can allocate its own pseudo-terminals via `posix_openpt`. Required by anything that spawns a shell (test runners, `git`, `gh`, REPLs, agent tools that wrap commands in a pty). Adds `(allow pseudo-tty)` and read/write/ioctl on `/dev/ptmx` to the generated profile. Set to `false` for a tighter sandbox when the inner command does not need to allocate new ttys. | +| `seatbelt.keychainAccess` | boolean | `false` | When `true`, opens the sandbox enough for `keytar` / `Security.framework` to reach the macOS Keychain end-to-end. Adds Mach lookup for `com.apple.SecurityServer`, `com.apple.securityd`, `com.apple.trustd`, `com.apple.ocspd`, `com.apple.cfprefsd.daemon`, `com.apple.xpcd`, and the `com.apple.lsd.*` family (regex); read access to `/private/var/db/mds` (Spotlight/MDS metadata) and `/private/var/protected/trustd` (trustd protected store); and read+write access to `~/Library/Keychains` (user keychain DB) and `/private/var/folders` (XPC cache and per-user containers). The system keychain stores under `/Library/Keychains` and `/System/Library/Keychains` are already covered by the baseline `/Library` and `/System` read-only allows. Off by default — opt in only when the inner workload genuinely needs Keychain access. | ### Filesystem policy diff --git a/sdk/src/sandbox.ts b/sdk/src/sandbox.ts index a031173b..e155fd60 100644 --- a/sdk/src/sandbox.ts +++ b/sdk/src/sandbox.ts @@ -122,18 +122,15 @@ function buildLinuxProcessConfig( * * The seatbelt backend's `sandbox-exec` reads a TinyScheme profile * generated server-side by `seatbelt_common::profile_builder`, so the SDK - * only needs to set the containment type and the mode selector under the - * experimental block — the policy fields on `ContainerConfig` (filesystem / + * only needs to set the containment type and ensure the top-level `seatbelt` + * config block exists — the policy fields on `ContainerConfig` (filesystem / * network / ui) drive the actual rules. */ function buildDarwinProcessConfig( config: ContainerConfig, ): ContainerConfig { config.containment = 'seatbelt'; - config.experimental = { - ...(config.experimental ?? {}), - seatbelt: {}, - }; + config.seatbelt = config.seatbelt ?? {}; return config; } diff --git a/sdk/src/types.ts b/sdk/src/types.ts index 1b1a6f06..63f0fa24 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -276,9 +276,16 @@ export interface ContainerConfig { experimental?: { /** WSLC SDK configuration for Linux containers from Windows */ wslc?: WslcConfig; - /** macOS sandbox configuration (macOS only) */ + /** + * macOS sandbox configuration (macOS only). + * @deprecated Use the top-level {@link seatbelt} field instead. This + * location is retained for backward compatibility and will be removed + * in a future schema version. + */ seatbelt?: SeatbeltConfig; }; + /** macOS Seatbelt sandbox configuration (macOS only) */ + seatbelt?: SeatbeltConfig; /** Cross-platform UI configuration */ ui?: UiConfig; } @@ -348,8 +355,8 @@ export interface LxcConfig { } /** - * macOS Seatbelt sandbox configuration. Used under - * `experimental.seatbelt` when containment is `'seatbelt'`. + * macOS Seatbelt sandbox configuration. Used under the top-level + * `seatbelt` key (preferred) or `experimental.seatbelt` (deprecated). */ export interface SeatbeltConfig { /** diff --git a/src/core/wxc_common/src/config_parser.rs b/src/core/wxc_common/src/config_parser.rs index ab7cb91c..1cddb190 100644 --- a/src/core/wxc_common/src/config_parser.rs +++ b/src/core/wxc_common/src/config_parser.rs @@ -228,6 +228,9 @@ struct RawConfig { network: Option, ui: Option, experimental: Option, + /// Top-level seatbelt config (preferred over experimental.seatbelt). + #[serde(alias = "macos_sandbox")] + seatbelt: Option, } // State-aware request shape. `phase` is required (no `#[serde(default)]` on @@ -618,13 +621,16 @@ fn present_backend_sections(raw: &RawConfig) -> Vec<&'static str> { if experimental.wslc.is_some() { push(ContainmentBackend::Wslc); } - if experimental.seatbelt.is_some() { + if experimental.seatbelt.is_some() && raw.seatbelt.is_none() { push(ContainmentBackend::Seatbelt); } if experimental.isolation_session.is_some() { push(ContainmentBackend::IsolationSession); } } + if raw.seatbelt.is_some() { + push(ContainmentBackend::Seatbelt); + } sections } @@ -1211,6 +1217,26 @@ fn convert_raw_config_inner( ExperimentalConfig::default() }; + // Top-level `seatbelt` takes precedence over `experimental.seatbelt`. + // This supports the two-phase migration: new configs use the top-level key, + // old configs still work via the experimental path. + let experimental = if let Some(raw_sb) = raw.seatbelt { + let sb = SeatbeltConfig { + profile_override: raw_sb.profile_override, + gui_access: raw_sb.gui_access.unwrap_or(false), + launch_method: raw_sb.launch_method.unwrap_or_default(), + nested_pty: raw_sb.nested_pty.unwrap_or(true), + keychain_access: raw_sb.keychain_access.unwrap_or(false), + extra_mach_lookups: raw_sb.extra_mach_lookups.unwrap_or_default(), + }; + ExperimentalConfig { + seatbelt: Some(sb), + ..experimental + } + } else { + experimental + }; + // UI section if let Some(raw_ui) = raw.ui { let clipboard = match raw_ui.clipboard.as_deref() { @@ -1281,6 +1307,7 @@ fn convert_raw_state_aware( // one-shot RawExperimental; it is preserved separately on // ParsedStateAwareRequest as raw JSON. experimental: None, + seatbelt: None, }; let require_process = phase == Phase::Exec; @@ -3312,6 +3339,36 @@ mod tests { assert!(cfg.keychain_access); } + #[test] + fn top_level_seatbelt_config_accepted() { + let json = r#"{"process": {"commandLine": "echo hi"}, "containment": "seatbelt", "seatbelt": {"nestedPty": false, "keychainAccess": true}}"#; + let encoded = base64_encode(json.as_bytes()); + let mut logger = test_logger(); + + let req = load_request(&encoded, &mut logger, true).unwrap(); + let cfg = req + .experimental + .seatbelt + .expect("top-level seatbelt should populate experimental.seatbelt internally"); + assert!(!cfg.nested_pty); + assert!(cfg.keychain_access); + } + + #[test] + fn top_level_seatbelt_takes_precedence_over_experimental() { + let json = r#"{"process": {"commandLine": "echo hi"}, "containment": "seatbelt", "seatbelt": {"nestedPty": false}, "experimental": {"seatbelt": {"nestedPty": true}}}"#; + let encoded = base64_encode(json.as_bytes()); + let mut logger = test_logger(); + + let req = load_request(&encoded, &mut logger, true).unwrap(); + let cfg = req + .experimental + .seatbelt + .expect("seatbelt config should be set"); + // Top-level wins over experimental.seatbelt + assert!(!cfg.nested_pty); + } + // Legacy wire-name aliases. The parser accepts the pre-0.6 wire vocabulary // (`appcontainer`, `macos_sandbox`, and the `appContainer` / // `experimental.macos_sandbox` sub-block keys) so that configs declaring @@ -3450,7 +3507,7 @@ mod tests { assert_multi_backend_rejected( "processcontainer", r#""experimental": {"seatbelt": {"guiAccess": true}}"#, - "experimental.seatbelt", + "seatbelt", ); } diff --git a/src/core/wxc_common/src/models.rs b/src/core/wxc_common/src/models.rs index f9f0f6e5..584b3d99 100644 --- a/src/core/wxc_common/src/models.rs +++ b/src/core/wxc_common/src/models.rs @@ -71,7 +71,7 @@ impl ContainmentBackend { ContainmentBackend::Lxc => Some("lxc"), ContainmentBackend::WindowsSandbox => Some("experimental.windows_sandbox"), ContainmentBackend::Wslc => Some("experimental.wslc"), - ContainmentBackend::Seatbelt => Some("experimental.seatbelt"), + ContainmentBackend::Seatbelt => Some("seatbelt"), ContainmentBackend::IsolationSession => Some("experimental.isolation_session"), ContainmentBackend::Bubblewrap | ContainmentBackend::Hyperlight @@ -81,8 +81,9 @@ impl ContainmentBackend { } } -/// Configuration specific to the Seatbelt backend (experimental). -/// Used under `experimental.seatbelt` when `containment == Seatbelt`. +/// Configuration specific to the Seatbelt backend. +/// Used under `seatbelt` (preferred) or `experimental.seatbelt` (deprecated) +/// when `containment == Seatbelt`. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct SeatbeltConfig { @@ -548,7 +549,7 @@ pub struct ExperimentalConfig { /// Isolation Session backend (experimental). #[serde(rename = "isolation_session")] pub isolation_session: Option, - /// Seatbelt (macOS) backend (experimental). + /// Seatbelt (macOS) backend. Deprecated location — prefer top-level `seatbelt`. pub seatbelt: Option, }