fix(sandbox): honour the requested cwd under bubblewrap - #4
Merged
voidstackloop merged 1 commit intoJul 25, 2026
Merged
Conversation
wrapCommand always passed `--chdir <workspaceRoot>`. That takes precedence
over the working directory bubblewrap inherits from the spawning process,
so it silently overrode the subdirectory the caller had already resolved
and set on the spawn options.
The result was a silent platform split for the same tool call:
run_command({cwd: "packages/api"}) ran in packages/api on macOS
(sandbox-exec adds no chdir, so the inherited cwd stands) and when
unsandboxed, but in the workspace root on Linux with bubblewrap available.
create_terminal({cwd}) had the same problem via terminal-manager.
wrapCommand now takes the start directory as an option and chdirs to it,
defaulting to the workspace root when unset. All three callers already
resolve it through resolveSafePath, and the writable bind mount is still
only the workspace root, so this does not widen what the sandbox allows.
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.
Problem
wrapCommandalways appended--chdir <workspaceRoot>to the bubblewrap invocation.--chdirtakes precedence over the working directory bubblewrap inherits from the spawning process, so it silently overrode the subdirectory the caller had already resolved and passed on the spawn options.runCommandresolvescwdthroughresolveSafePathand hands it toexecAsync; under bubblewrap that value was discarded.createTerminaldoes the same thing viaptyOptions.cwd.The result is one tool call behaving three ways:
run_command({cwd: "packages/api"})runs inpackages/apisandbox-execadds no chdir)packages/apipackages/apiSo builds and tests silently run in the wrong directory on exactly the configuration where the sandbox is working, which is also the hardest one to notice.
Fix
WrapCommandOptionstakes an optionalcwd, and the bubblewrap branch chdirs to it, defaulting to the workspace root when unset. All three call sites (runCommand,startBackgroundCommand,createTerminal) pass the value they had already resolved.Not a widening of the sandbox: all three callers derive that path from
resolveSafePath, which enforces lexical containment and a realpath check against symlink escapes, and the writable bind mount is still only the workspace root.--chdiris kept rather than dropped — removing it would make the working directory depend on whether the path survived the bind mounts, which is a subtler version of the same bug.sandbox-execis deliberately untouched; it inherits the parent's cwd and the callers already set it.Tests
--chdir, and the workspace root is still what gets bound writable.cwd,--chdirstill points at the workspace root.Control experiment: the first test fails with
--chdirpinned back to the root, and passes with the patch.I could not execute bubblewrap to demonstrate this end-to-end — it can't create user namespaces in my environment, which is the same AppArmor restriction
command-sandbox.ts:27-32already describes. The behaviour rests on documented--chdirsemantics (man bwrap: "Change directory to DIR";$HOMEis only consulted "if--chdirhas not been explicitly specified"), and the tests assert the constructed argument vector.npx tsc -p tsconfig.json --noEmitclean;npm test229 passed.