fix(sandbox): allow realpath(cwd) so bun run/bunx work in enforcing workspaces#36
Merged
Merged
Conversation
…stor dirs
Enforcing mode denied `bun run`/`bunx` — and anything that realpath(3)s the
cwd — with EPERM ("error loading current directory" /
"CouldntReadCurrentDirectory"). The profile allows file-read-metadata +
file-test-existence globally but file-read* (which includes opening a
directory) only on the workspace, runtime, system roots, and `(literal "/")`
— not the workspace's intermediate ancestor dirs. realpath(3) must open()
each ancestor to canonicalize the cwd, so it hit `(deny default)`.
Grant `(allow file-read* (literal <dir>))` on each strict ancestor of the
workspace root + composition members, up to but not including "/". `literal`
(the node only, not `subpath`) permits traversal/enumeration of the path
components without exposing sibling subtrees' contents — the same trade-off
as the existing `(literal "/")` grant, one level down. A shared helper
(`workspace_ancestor_dirs`) keeps render_profile and compute_monitor_policy's
would-block classifier in sync; unit tests cover the helper + classifier.
Fixes simion#35
simion
added a commit
that referenced
this pull request
Jun 14, 2026
Completion sound (#34): play the sound directly via afplay instead of relying on the desktop notification's sound field. mac-notification-sys rides the deprecated NSUserNotification API, which drops the banner sound on modern macOS, so Preview and real completions were silent. New play_completion_sound Rust command resolves the macOS sound name via the Library/Sounds search path and plays it on a detached thread; the banner still fires but silent. Sandbox bun/realpath (#35, #36): grant each strict workspace-ancestor directory read as (literal ...) so realpath(3) on the cwd can open() the path components. Without it, bun run / bunx (and any tool that canonicalizes the cwd) failed at startup with CouldntReadCurrentDirectory in enforcing workspaces. Shared workspace_ancestor_dirs helper keeps render_profile and compute_monitor_policy in sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Owner
|
Thanks for contributing! tested locally and it works accordingly. 🍻 |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Fixes #35.
Problem
In an enforcing workspace,
bun run <script>andbun x <pkg>fail at startup with:More generally, anything that canonicalizes the cwd with
realpath(3)fails — which includes the Bun-compiled agent CLIs'bun run/bunxsubprocesses and any Husky/git hook that usesbunx.Root cause
render_profileallowsfile-read-metadata+file-test-existenceglobally, butfile-read*(which includesfile-read-data, i.e. opening a directory) only on the workspace, runtime, system roots, and(literal "/"). The workspace's intermediate ancestor directories (/Users,/Users/<me>, … down to the workspace's parent) get only metadata/existence.realpath(3)on the cwd has toopen()each ancestor directory to resolve the path — afile-read-dataop on each — which the allow-list denies → EPERM. It's the exact(literal "/")situation one level down: the components between/and the workspace are traversable (metadata is global) but not openable, andrealpath(3)needs the open.Empirically (inside the cage):
realpath(".")→ EPERM, whilefcntl(F_GETPATH),getattrlist(ATTR_CMN_FULLPATH),getcwd, and a userspacelstat-walk all succeed (they don't open ancestors). That's whybun --version/bun pm ls/Bun.spawnSyncwork butbun run/bun xdon't, and whybun xfrom/tmpworks (/private,/are broadly readable).Fix
Grant
(allow file-read* (literal "<dir>"))on each strict ancestor of the workspace root (and each composition member), up to but not including/. Usingliteral(the directory node only, notsubpath) letsrealpath/traversal open the path components without exposing sibling subtrees' contents — the same trade-off as the existing(literal "/")grant, one level down. Sibling names are already enumerable via the global metadata allow, so this leaks no new secrets (sibling CONTENTS stay denied by(deny default)).A shared helper,
workspace_ancestor_dirs, is used by bothrender_profile(enforcement) andcompute_monitor_policy(the would-block classifier) so the two stay in sync —MonitorPolicygains aread_literalsset checked with exact-match (not the subtreeunder()check) so monitoring mode doesn't mis-classify these ancestor reads.Testing
cargo test --lib sandbox::→ 55 pass (52 existing + 3 new):ancestor_dirs_walks_up_to_but_not_rootworkspace_ancestor_dirs_strict_ancestors_onlywould_block_allows_ancestor_node_read_not_subtreecargo checkclean;cargo clippy --libintroduces no new warnings (the pre-existing ones are untouched).would_block_classifies_pure_allowlisttest was updated for the newread_literalsfield (set tovec![], behavior unchanged).I verified the root cause end-to-end on a live enforcing workspace (the
realpathEPERM repro in #35) and confirmed the fix follows the existing(literal "/")pattern, but I could not run a full signed app build in my environment — CI / a maintainer build is the final confirmation that a real sandboxedbun runnow succeeds.By opening this PR I agree to the CLA per CONTRIBUTING.md.