You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds 1.1.0-alpha Seatbelt settings that let a GUI app be granted only the Mach services, IOKit classes, and POSIX shared memory it actually needs.
1. Custom lists (feature)
New seatbelt fields in the 1.1.0-alpha contract:
Field
Controls
Shape
customMachLookups
Mach services the app may look up
default (deny/allow), allow, deny
customMachRegisters
Mach services the app may register
global and local blocks, each with the same shape
customIoKits
IOKit user-client classes the app may open
default, allow, deny
customPosixShm
POSIX shared memory names
default, read, readWrite, own, deny
An entry ending in * matches a prefix. deny entries win over everything else.
Each block that is present replaces the matching guiAccess grant. The blocks also work without guiAccess.
extraMachLookups is now documented as deprecated in favor of customMachLookups.
The schema and Node wire types are regenerated. Older contracts (1.0.0, 0.9.0-alpha) reject these fields.
2. Explicit settings win over guiAccess (bug fix)
Before this change, turning on guiAccess quietly undid some settings written in the config. Now, in every contract version:
nestedPty: false blocks PTY creation.
keychainAccess off (the default) blocks the Keychain servers, even though guiAccess allows all Mach lookups.
ui.clipboard: "none" blocks every com.apple.pasteboard.* service, not only com.apple.pasteboard.1.
readonlyPaths and deniedPaths entries inside /private/tmp and /private/var/folders stay enforced.
versioning.md records the nestedPty, Keychain, and path changes as a V1 servicing exception.
3. Validation
The config is rejected before the sandbox is created when:
a custom block is combined with profileOverride, or customMachLookups with extraMachLookups;
an entry is malformed: a bare *, a * that isn't at the end, an IOKit entry that isn't a class name, or a shared memory name over 31 bytes;
a customMachLookups entry uses MXC's reserved com.microsoft.mxc.run. prefix;
an allow entry names only something a setting denies (for example com.apple.SecurityServer without keychainAccess);
a deny entry would block something a setting grants (for example the pasteboard with ui.clipboard enabled, IOHIDLibUserClient with ui.injection: true, or com.apple.trustd with keychainAccess: true).
4. Learning mode (captureDenials)
In "allow" mode, every custom list acts as default: "allow", so the app runs and the report lists each name to add. deny entries still apply.
Settings omitted from the config allow and report. Settings written in the config, including nestedPty: false, stay enforced.
Accesses that only guiAccess's broad grants allow are reported in the verbose report, so you can see which names to move into the custom lists.
I created a simple demo to show Microsoft Edge functioning inside MXC with just its required Mach/IOKit/POSIX grants. The grants were found using learning mode.
New end-to-end coverage, wired into the nightly Seatbelt run through run_seatbelt_all_tests.sh:
each custom block on its own, and all four together with several entries each, in block and allow mode;
each guiAccess override fix (nestedPty, keychainAccess, clipboard, /private/tmp read-only paths);
learning-mode reports for omitted and written clipboard, Keychain, and nestedPty settings.
The nestedPty: false allow-mode regression was confirmed to fail before its fix (PTY_OK) and pass after it (PTY_BLOCKED).
Not run: Windows and Linux runtime suites (this change is Seatbelt-only; both targets were checked with Clippy) and the .NET SDK tests (no .NET changes).
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Previously valid V1 requests now fail validation without explicit maintainer acceptance of the compatibility break, and custom-policy validation inconsistencies remain unresolved.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Policy hashes conflate settings with different access decisions, and report redaction hides leading-slash shared-memory names needed to configure custom grants.
[P2] Shared-memory accesses allowed by these new :grant rules appear only in the verbose report, but capture_analysis::report_signature redacts any target containing /. A name such as /myapp.region is therefore unavailable in either JSON report, preventing callers from discovering names for customPosixShm; the custom default: "allow" grant has the same issue. Preserve shared-memory names when redacting filesystem paths, and add a regression test for a grant with a leading-slash name.
Include effective settings in policy hash projection
[P2] In captureDenials allow mode, omitting ui.clipboard versus writing "none" produces the same mxc.PolicyHash, although one permits access and the other blocks it; omitted versus explicit keychainAccess: false has the same problem. policy_identity::policy_projection serializes these structs, so both new presence fields are excluded by #[serde(skip)]. Include the effective setting decisions explicitly in the hash projection, keep the fields out of transport serialization, and add hash regression tests for these pairs.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Maintainer review is needed because the new Mach, IOKit, and shared-memory controls lack a linked approved design, and their native enforcement was not independently validated on supported macOS hosts.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Maintainer review is needed to confirm the new macOS sandbox controls’ security semantics and grant/deny precedence, as no approved design is linked and runtime enforcement could not be reproduced in this environment.
Versioning: Please keep parity with existing behavior for V1 and below, including 1.0.0 and 0.9.0-alpha. Start enforcing the stricter permissions in 1.1+ (1.1.0-alpha onward): deny by default and require callers to explicitly enable the access they need.
With GUI and injection enabled, a custom IOKit list removes the blanket grant without restoring IOHIDLibUserClient, silently disabling injection. Emit the enabled HID-specific grant independently of the custom list and add a behavioral regression.
In learning mode, omitting keychainAccess permits and reports access, but adding com.apple.SecurityServer to customMachLookups.allow rejects the request. Align validation with enforcement's capture-mode and presence rules; keep explicit restrictions enforced.
Custom-policy rejection is tested through helpers, not the backend entry point. Removing this validation call could leave the tests green. Add end-to-end rejection cases for malformed/conflicting policies and assert that the workload never executes.
Parsed explicit ui.disable and ui.injection restrictions lack learning-mode regression coverage; manually setting presence flags in profile tests does not protect parsing. Test omitted fields against explicit disable: true and injection: false through parsing and enforcement.
Shared-memory denial probes use O_RDWR and do not test unlink. Read denial can hide an unintended write grant. Precreate regions and independently assert read-only, write-only and unlink denial, including overlapping grants and learning mode.
Shared normalization converts omitted custom-list defaults into explicit Deny, losing presence and placing backend policy defaulting in the parser. Preserve the optional value through normalization and resolve the default in Seatbelt's effective-policy construction.
The learning-mode promise that written settings remain enforced is too broad: an explicit network egress default of deny withholds an allow, so learning mode's allow default permits traffic. Enumerate preserved restrictions and explicitly document exceptions for network and custom-list denying defaults.
Describing custom default-deny as granting only the allowlist is inaccurate: baseline grants and enabled-setting grants remain. Clarify that each list replaces its corresponding broad GUI grant, not every grant in that category.
The wildcard error recommends default: "allow" even for shared-memory read or deny entries. That can add write/create permissions or reverse the intended denial. Make the remedy list-specific and recommend concrete names or supported prefixes instead.
—
Review scope: source inspection at 45044210; macOS runtime enforcement was not independently reproduced. Items 3–5 are regression-coverage gaps, and item 6 is an architectural/presence issue, not a separately demonstrated runtime failure. Numerical confidence scores for the Medium findings were not retained in the consolidated report.
This branch has not been deployed
No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📖 Description
Adds
1.1.0-alphaSeatbelt settings that let a GUI app be granted only the Mach services, IOKit classes, and POSIX shared memory it actually needs.1. Custom lists (feature)
New
seatbeltfields in the1.1.0-alphacontract:customMachLookupsdefault(deny/allow),allow,denycustomMachRegistersglobalandlocalblocks, each with the same shapecustomIoKitsdefault,allow,denycustomPosixShmdefault,read,readWrite,own,deny*matches a prefix.denyentries win over everything else.guiAccessgrant. The blocks also work withoutguiAccess.extraMachLookupsis now documented as deprecated in favor ofcustomMachLookups.1.0.0,0.9.0-alpha) reject these fields.2. Explicit settings win over
guiAccess(bug fix)Before this change, turning on
guiAccessquietly undid some settings written in the config. Now, in every contract version:nestedPty: falseblocks PTY creation.keychainAccessoff (the default) blocks the Keychain servers, even thoughguiAccessallows all Mach lookups.ui.clipboard: "none"blocks everycom.apple.pasteboard.*service, not onlycom.apple.pasteboard.1.readonlyPathsanddeniedPathsentries inside/private/tmpand/private/var/foldersstay enforced.versioning.mdrecords thenestedPty, Keychain, and path changes as a V1 servicing exception.3. Validation
The config is rejected before the sandbox is created when:
profileOverride, orcustomMachLookupswithextraMachLookups;*, a*that isn't at the end, an IOKit entry that isn't a class name, or a shared memory name over 31 bytes;customMachLookupsentry uses MXC's reservedcom.microsoft.mxc.run.prefix;allowentry names only something a setting denies (for examplecom.apple.SecurityServerwithoutkeychainAccess);denyentry would block something a setting grants (for example the pasteboard withui.clipboardenabled,IOHIDLibUserClientwithui.injection: true, orcom.apple.trustdwithkeychainAccess: true).4. Learning mode (
captureDenials)"allow"mode, every custom list acts asdefault: "allow", so the app runs and the report lists each name to add.denyentries still apply.nestedPty: false, stay enforced.guiAccess's broad grants allow are reported in the verbose report, so you can see which names to move into the custom lists.🔗 References
docs/backends/seatbelt/seatbelt-backend.md("Custom Mach, IOKit, and shared memory lists"),docs/schema.md,docs/development/architecture/versioning.md.Test It Yourself Here:
I created a simple demo to show Microsoft Edge functioning inside MXC with just its required Mach/IOKit/POSIX grants. The grants were found using learning mode.
Download this: seatbelt_msft_edge 2.zip
Run
./run_sandboxed_edge.sh <path-to-mxc-exec-mac>You can either quit MXC or ctrl + click Edge on the dock and press Quit to end the demo.
🔍 Validation
Run on macOS (Apple silicon) at the head of this branch.
src/unless noted)cargo fmt --all -- --checkcargo clippy -p mxc-sdk -p mxc_darwin -p mxc_ffi --all-targets -- -D warningscargo clippy -p mxc-sdk --lib --tests --target x86_64-unknown-linux-gnu -- -D warningscargo clippy -p mxc-sdk --lib --tests --target x86_64-pc-windows-msvc -- -D warningscargo test -p mxc-sdk --libcargo test -p mxc-sdk --test mxc_contract_v1_1_0_alphacargo test -p mxc-sdk --test mxc_contract_v1_0_0cargo test -p mxc-sdk --test mxc_contract_v0_9_0_alphacargo test -p mxc-sdk --test mxc_contract_version_boundariescargo test -p mxc-sdk --test streamingnode scripts/versioning/validate-configs.js(repo root)npx tsc --noEmit -p .(sdk/node/)./build-mac.sh(repo root)bash tests/scripts/run_seatbelt_gui_access_test.shbash tests/scripts/run_seatbelt_capture_denials_test.shbash tests/scripts/run_seatbelt_options_test.shbash tests/scripts/run_seatbelt_tty_test.shbash tests/scripts/run_seatbelt_ui_test.shbash tests/scripts/run_seatbelt_examples_test.shNew end-to-end coverage, wired into the nightly Seatbelt run through
run_seatbelt_all_tests.sh:guiAccessoverride fix (nestedPty,keychainAccess, clipboard,/private/tmpread-only paths);nestedPtysettings.The
nestedPty: falseallow-mode regression was confirmed to fail before its fix (PTY_OK) and pass after it (PTY_BLOCKED).Not run: Windows and Linux runtime suites (this change is Seatbelt-only; both targets were checked with Clippy) and the .NET SDK tests (no .NET changes).
✅ Checklist
feature-acceptedCargo.lock, thedependency-feed-checkcheck passes (see pull request builds)📋 Issue Type
Microsoft Reviewers: Open in CodeFlow