Skip to content

fix(seatbelt): widen per-user Darwin temp grant to its container#659

Open
caarlos0 wants to merge 5 commits into
microsoft:mainfrom
caarlos0:macos-tmpdir
Open

fix(seatbelt): widen per-user Darwin temp grant to its container#659
caarlos0 wants to merge 5 commits into
microsoft:mainfrom
caarlos0:macos-tmpdir

Conversation

@caarlos0

@caarlos0 caarlos0 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

📖 Description

On macOS, $TMPDIR is the per-user _CS_DARWIN_USER_TEMP_DIR (/var/folders/<a>/<b>/T/). Its siblings under the same per-user container — C (_CS_DARWIN_USER_CACHE_DIR) and 0 (misc) — are left unwritable when a sandbox is granted read-write access to only the temp leaf, so tools that stage under the per-user cache get denied.

This expands a read-write grant that targets a per-user Darwin temp/cache leaf into read-write grants for its three well-known per-user siblings (.../T, .../C, .../0) in the Seatbelt profile builder (write_filesystem_allow). Because the expansion lives in the profile builder, it applies to every consumer of the macOS backend (CLI, Node SDK, C#/FFI, Rust SDK) — none of them has to carry macOS /var/folders path knowledge.

The expansion is strictly guarded by a new darwin_temp_sibling_grants helper:

  • It expands only when the path is exactly /var/folders/<a>/<b>/<leaf> with <leaf> one of T, C, or 0 (optionally under /private, the post-canonicalization form) and <a>/<b> are ordinary segments (never empty, ., or ..).
  • It grants only those three siblings — never the enclosing /var/folders/<a>/<b> container itself, so no other directory under the container becomes writable and a caller keeps least-privilege access to everything outside the T/C/0 triad.
  • A too-shallow path, a deeper path, a non-temp leaf, a ./.. traversal segment, or anything outside /var/folders yields no expansion — so an expansion can never reach the container root, /var/folders (every user's containers), or /.
  • deniedPaths still override the expanded grants, since denies are emitted after allows (Seatbelt last-match-wins), so a caller can carve any single sibling back out.
  • It is applied unconditionally in the platform-agnostic profile builder because a Seatbelt profile is always a macOS artifact regardless of build host — matching the existing guiAccess /private/var/folders grant.

The change only adds sibling grants; $TMPDIR (=.../T) itself was already granted, so no previously-working sandboxed command can break.

🔗 References

No linked issue. The macOS backend filesystem behavior is documented in docs/macos-support/seatbelt-backend.md, updated in this PR.

🔍 Validation

All on a macOS host (aarch64-apple-darwin), run from src/:

  • cargo test -p seatbelt_common — 49 passed, incl. tests for the sibling expansion, non-per-user rejection (including ./.. traversal), a profile-integration test asserting all three siblings are granted while the bare container is not, and a deny-after-allow test proving a deniedPaths entry on a sibling still wins.
  • cargo fmt -p seatbelt_common -- --check — clean.
  • cargo clippy -p seatbelt_common --all-targets -- -D warnings — clean.

✅ Checklist

  • Signed the Contributor License Agreement
  • Linked to an issue
  • Updated documentation (if applicable)
  • Updated Copilot instructions (if build, architecture, or conventions changed)
  • If this PR changes Cargo.lock, the dependency-feed-check check passes (no dependency change)

📋 Issue Type

  • Bug fix
  • Feature
  • Task
Microsoft Reviewers: Open in CodeFlow

On macOS `$TMPDIR` is the per-user `_CS_DARWIN_USER_TEMP_DIR`
(`/var/folders/<a>/<b>/T/`); its siblings `C` (`_CS_DARWIN_USER_CACHE_DIR`)
and `0` (misc) share the same per-user container but were left unwritable
when only the temp leaf was granted, so sandboxed tools that stage under the
per-user cache were denied.

Widen a read-write grant on a `T`/`C`/`0` leaf to its enclosing
`/var/folders/<a>/<b>` container in the Seatbelt profile builder, so all three
siblings are covered by one grant. The guard is strict — only a direct
`T`/`C`/`0` child of a genuine two-segment per-user container (optionally under
`/private`, the post-canonicalization form) qualifies, so a grant can never
widen up to `/var/folders` (every user) or `/`. `deniedPaths` still override,
since denies are emitted after allows (last-match-wins).

Applied unconditionally in the platform-agnostic profile builder because a
Seatbelt profile is always a macOS artifact regardless of build host, matching
the existing `guiAccess` `/private/var/folders` grant.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4e17ae47-34f1-4c15-8f22-8eb86f7b12a5
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 17, 2026 19:07
@caarlos0
caarlos0 requested a review from a team as a code owner July 17, 2026 19:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts the macOS Seatbelt profile generator to broaden certain readwritePaths entries that target per-user Darwin temp/cache leaves under /var/folders/<a>/<b>/{T,C,0} so the enclosing per-user container is granted instead, preventing cache-related write denials in sandboxed tools. The change is implemented in the profile builder so it benefits all macOS backend consumers (CLI/SDKs/FFI) without duplicating Darwin path knowledge.

Changes:

  • Widen readwritePaths grants for /var/folders/<a>/<b>/{T,C,0} (and /private/...) to /var/folders/<a>/<b> in the Seatbelt profile builder.
  • Add unit tests validating widening behavior and non-matching cases.
  • Document the widening behavior in the Seatbelt backend docs.
Show a summary per file
File Description
src/backends/seatbelt/common/src/profile_builder.rs Adds Darwin temp/cache leaf widening logic in write_filesystem_allow plus new helper + tests.
docs/macos-support/seatbelt-backend.md Documents the readwritePaths widening behavior and its safety constraints.

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment thread src/backends/seatbelt/common/src/profile_builder.rs Outdated
caarlos0 and others added 2 commits July 17, 2026 16:13
darwin_temp_container_grant accepted any non-empty <a>/<b> segment, so a
path-traversal input like /var/folders/ab/../T widened to /var/folders/ab/..
(effectively /var/folders), over-broadening the grant and contradicting the
documented "never widen up to /var/folders or /" guarantee. Genuine Darwin
per-user container segments are never . or .., so reject those (and empty)
segments before widening, and cover the traversal cases in the rejection test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4e17ae47-34f1-4c15-8f22-8eb86f7b12a5
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Express the .-/..-rejection with matches!(*s, "." | ".."), matching the
leaf check in the same function. Behavior-preserving.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4e17ae47-34f1-4c15-8f22-8eb86f7b12a5
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Comment thread src/backends/seatbelt/common/src/profile_builder.rs Outdated
Comment thread src/backends/seatbelt/common/src/profile_builder.rs Outdated
caarlos0 and others added 2 commits July 22, 2026 09:33
Addresses review feedback: widening a per-user Darwin temp/cache leaf grant
to the whole '/var/folders/<a>/<b>' container made every directory under it
writable, not just the T/C/0 siblings, so callers lost least-privilege access
to other subdirs. Expand instead to exactly the three well-known siblings
(.../T, .../C, .../0) and never grant the container itself.

Also add the requested test: granting the T leaf allows the C sibling, and a
deniedPaths entry on C still wins via last-match ordering (allow-then-deny).

Rename darwin_temp_container_grant -> darwin_temp_sibling_grants (now returns
the three sibling paths) and update the backend doc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4e17ae47-34f1-4c15-8f22-8eb86f7b12a5
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
@caarlos0

Copy link
Copy Markdown
Contributor Author

@huzaifa-d thank you, resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants