Repository navigation
feat(tool): run shell commands under the kernel boundary when asked - #133
Merged
Merged
Conversation
The second half of the command boundary: `bash` now goes through the launcher that landed with the mechanism, behind a config switch. - `sandbox.confine_commands` turns it on; the policy lives in the tool package next to the other capability facts. - `bashInvocation` wraps the same ['bash','-c',cmd] argv the tool always ran. The mode follows the run: plan is read-only and grants no path, build is workspace-write over exactly WritableRoots(). Nothing else about the command changes, and with the switch off the argv is byte-for-byte what it was. - A launcher failure is reported as "the command did not run", not as a command failure, and only the launcher's own diagnostic lines are shown: the command never ran, so its output must not be attributed to it. Both the exit status and our prefix are required, so a command that exits 125 by itself is not read as a sandbox report. - `/sandbox` reports the switch and whether the host can honour it. Confining a subprocess is a different capability from confining our own opens, so it gets its own verdict: a host can have one and not the other. - Off by default, deliberately: a confined command can write only under the session's writable roots, so toolchains that write their own caches break. It is a choice that says what it costs, not a silent default. Refs #121. Landing plan: #127.
This was referenced Oct 8, 2026
Closed
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.
What changed
The second half of the command boundary:
bashnow goes through the launcher that landed in #132.config/config.go—sandbox.confine_commands; a bool overlay can only turn it on.tool/containment.go— the policy switch plusCommandConfinementAvailable(), which is a different question fromContainmentInfo(): a host can enforce the agent's own opens (the macOS component walk) and still have no way to confine a subprocess.tool/sandboxexec.go—bashInvocationwraps the same['bash','-c',cmd]argv;SandboxLauncherDiagnosticsreturns only the launcher's own lines.tool/bash.go— uses the wrapped argv; a launcher failure is reported as "the command did not run", never as a command failure.tool/confinement_{linux,other}.go— the launcher-capability probe, answered once per process.tui/sandbox.go—/sandboxreports the switch and whether the host can honour it.Refs #121. Landing plan: #127.
Why this shape
read-onlyand grants no path; build mode isworkspace-writeover exactlyWritableRoots()— the same list the file fence consumes, which is what [security] Derive the writable roots once and share them between the file fence and command execution #120 exists for.GOCACHE,~/.npm, …) fail. Turning it on is a choice that says what it costs; on a host with no mechanism, every command is refused rather than run unconfined.Verification
go test ./... -count=1→ 13 packages ok / 0 failed;-race→ 13 ok / 0 failed;make staticcheckexit 0;GOOS=linux GOARCH=arm64 go vet ./...clean;gofmt -lempty.--mode read-onlywith no--allow, build mode ⇒--mode workspace-writewith one--allowper writable root, both keeping the original command after the separator;SandboxLauncherDiagnosticskeeps only prefixed lines, so a command that prints words resembling our diagnostic contributes nothing.Honest scope
confine-*CI gate does not exist. That is the last piece of [security] bash has no kernel-enforced file boundary — the guard is a substring denylist #121 and must land before the issue closes: a stub mode whose command writes one file inside the project and one outside, with the wrapper shell printing the verdict, so the assertion is selective and a fail-closed refusal cannot masquerade as a working boundary.