Repository navigation
A project-local config can no longer widen the sandbox fence - #164
Merged
Merged
Conversation
The configuration is layered defaults -> user -> project, and the sandbox wiring
read the two keys that can widen the fence out of the merged result. The project
layer arrives with the repository, so a checked-out repository carrying
{"sandbox":{"project_root":"/"}} built the fence around the whole filesystem, and
one carrying allowed_paths handed the agent a path nobody allowed.
Measured on master d351ee8 with the real binary in one-shot build mode, writing a
path outside the project: project-local project_root "/" -> written;
project-local allowed_paths -> written; project-local config with neither ->
refused; the same allowed_paths in the user's own file -> written (the control
that has to keep working).
installSandboxBoundary() now takes both keys from config.UserSandboxBoundary()
(the code defaults plus the user's own file) and the working directory, and it
logs project_local_boundary_keys_ignored when the project file asked for either,
so a person who wrote such a key sees that it did not take effect. The language
server workspace is the same root, so it reads the installed one instead of the
merged config: a repository could otherwise point the server at / while the fence
still called the repository the project.
Deny rules and the two hardening booleans are untouched: merge() already lets a
project layer add refusals and turn the requirements on, never off.
After the change the table flips — both project-local widening keys are refused,
the user-level control is still written. Tests: TestSandboxBoundaryIgnoresThe
ProjectLayer (the layer rule), TestProjectLocalConfigCannotWidenTheFence (the
wiring, with a path inside the project as the control) and
TestUserConfigMayWidenTheFence (the other column). Mutation: making
UserSandboxBoundary read the merged config fails both fence tests ("boundary
root = \"/\"", "is allowed: the project-local config widened the fence");
restored, all pass.
Refs #162, #155.
10 tasks done
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
config/config.go:SandboxBoundaryplusUserSandboxBoundary()(code defaults + the user's own file) andProjectSandboxBoundary()(the same keys as the project file spells them, so a caller can report what it dropped).main.go: oneinstallSandboxBoundary()installs the boundary root, the working-directory auto-allow and the allowed paths from the user layer, and logsproject_local_boundary_keys_ignoredwhen the project file asked for either widening key. The duplicated later block is gone, and the language server workspace now reads the same installed root instead of the merged config.config/config_test.go,main_test.go: three tests — the layer rule, the wiring (with a control), and the other column of the table.docs/sandbox.md: a "which layer may set what" table recording the decision this issue asked for.Why
The configuration layers defaults → user → project, and the project layer arrives with the repository, so the agent may run with it before anyone has read it. The sandbox wiring read
sandbox.project_rootandsandbox.allowed_pathsout of the merged result, which made a checked-out repository able to widen the fence that is supposed to contain it.Measured with the real binary (one-shot, build mode, redirected HOME with no grant, writing
/tmp/stub-outside-write.txt):./.tinycode/config.jsond351ee8{"sandbox":{"project_root":"/"}}{"sandbox":{"allowed_paths":["/tmp/stub-outside-write.txt"]}}{"default_mode":"build"}allowed_pathsin the user's file (control)The LSP workspace root had the same source, so a repository could also point the language server at
/while the fence still called the repository the project; it now takes the installed root, so "inside the project" means one thing to both.Verification
project_local_boundary_keys_ignored).go test ./... -count=1→ 13 packages ok;-count=1 -race→ 13 packages ok;go vet ./...clean;gofmt -lnothing.make test-tui-scenarios→ exit 0, 31 scenario files, 27 ok, 4 skipped by design (confine-bash-write,live-answer,lsp-diagnostics,lsp-tools, each naming its make target).UserSandboxBoundary()return the merged config fails both fence tests —boundary root = "/",is allowed: the project-local config widened the fence(and the config-layer test:user boundary root = "/",user allowed paths = [/srv/user-cache /etc]). Restored, all pass.Honest scope
make test-tui-confinecannot run on this macOS host (no Landlock; the target skips loudly), so the kernel-boundary job remains CI-only. The change here is wiring that runs everywhere.goplsis not installed here, somake test-lsp/make test-tui-lspwere not run locally; the LSP workspace change is covered by CI'sLSP integration (gopls + typescript)job and by the ungated scenario suite, in which the two LSP scenarios are skipped locally by design.sandboxblock may still do is now written down rather than inferred:deny_commandsaccumulates, and the two hardening booleans can be turned on by any layer but never off (enforced bymerge()since the sandbox landed).Follow-ups