Skip to content

fix(sandbox): confine the daemon's /read route to app root for absolute paths - #7130

Closed
pedrofrxncx wants to merge 1 commit into
mainfrom
fix/daemon-read-absolute-path-escape-w2
Closed

pedrofrxncx wants to merge 1 commit into
mainfrom
fix/daemon-read-absolute-path-escape-w2

Conversation

@pedrofrxncx

@pedrofrxncx pedrofrxncx commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Found while hunting the studio-fs-abstraction focus area (the fs abstraction itself, packages/sandbox/daemon-go/internal/routes/fs.go, is the real implementation of issue #2884 — the frontend apps/web/src/lib/filesystem.ts hinted at doesn't exist and a prior audit confirmed the abstraction is already mature). Reading the route while auditing that area surfaced a real trust-boundary gap.

The bug: every fs mutation route (/write, /edit, /mkdir, /rename, /delete) confines a caller-supplied path via paths.SafePath(appRoot, repoDir, path), which checks even an absolute path is inside appRoot. /read had its own resolveReadPath wrapper that special-cased absolute paths to skip that check entirely and pass them straight to os.Stat/read. A path in a /read request crosses a real trust boundary — it comes from the agent's tool call over the daemon's HTTP API — so an absolute path let a run read any file the daemon process can see (secrets, other repos on the same pod, etc.), not just files under the sandbox's app root.

Failure scenario: POST /read with {"path": "/etc/passwd"} (or any absolute path outside appRoot) returned 200 with the file's content instead of the 400 every other fs route already gives for the same shape of request.

Fix: deleted resolveReadPath's bypass and route /read through the same paths.SafePath call the write-side routes use. Net: -13/+... a small deletion, no new abstraction.

Regression test: TestReadRefusesAbsolutePathOutsideRoot in fs_test.go — writes a secret file outside appRoot (via os.MkdirTemp, not t.TempDir(), so it's genuinely outside the shared per-test temp root) and asserts /read now 400s and never echoes the content. Confirmed it fails against the pre-fix code (200 + leaked content) and passes after.

A reviewer can confirm with:

cd packages/sandbox/daemon-go && go test ./internal/routes/ -run TestRead -v

Locally ran: go build ./..., go vet ./internal/routes/..., gofmt -l (clean), and the full internal/routes package test suite (all pass, 2 skipped for missing local rg). Full CI covers the rest.


Summary by cubic

Fixes a path traversal in the daemon's /read route so an absolute path can no longer read files outside the sandbox's app root. Previously /read special-cased absolute paths and passed them straight to os.Stat, while every other fs route already confined them with paths.SafePath.

  • /read now runs through the same paths.SafePath containment as the write-side routes and returns 400 for out-of-root paths.
  • Adds a regression test that writes a secret file outside appRoot and asserts /read returns 400 without leaking its content.

Written for commit 9dea9f5. Summary will update on new commits.

Review in cubic

…te paths

The /read route special-cased absolute paths to bypass paths.SafePath's
containment check entirely, while every other fs route (/write, /edit,
/mkdir, /rename, /delete) already runs absolute paths through SafePath.
An absolute `path` in a /read request could therefore read any file
reachable by the daemon process, not just files under AppRoot.

Removes resolveReadPath's IsAbs bypass and routes /read through the same
paths.SafePath(appRoot, repoDir, path) call as the write-side routes.

Added TestReadRefusesAbsolutePathOutsideRoot, which fails against the old
code (200 with the file's content) and passes against the fix (400,
no content leaked).
@pedrofrxncx

Copy link
Copy Markdown
Collaborator Author

Closing as stale: this PR sat past the bot's 48h merge window, main has moved on, and its CI results no longer reflect the current base. This is a housekeeping close, not a rejection of the change — if the underlying problem still exists, the bot will find it again and open a fresh, rebased PR.

[studio-bot:stale-close]

@pedrofrxncx
pedrofrxncx deleted the fix/daemon-read-absolute-path-escape-w2 branch September 14, 2026 14:01
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.

1 participant