Repository navigation
fix: sandbox agent bash and restrict ca --agent (VULN-1413) - #28
mitch-fultz wants to merge 2 commits into
Conversation
…ols (VULN-1413) Pipeline and interviewer agents ran Pi's bash with the host's credentials, env tokens, and network, so a prompt injection could reach RCE and credential theft. - Every role's bash now runs through @anthropic-ai/sandbox-runtime (bubblewrap on Linux, sandbox-exec on macOS): credential paths hidden, env reduced to an allowlist, writes limited to the repo, /tmp and the repo's git dirs (hooks/config read-only), egress limited to registry.npmjs.org, api.workos.com and *.authkit.app. The sandbox fails closed when its dependencies (or Linux seccomp) are missing. - Read/write/edit tools get the same path rules host-side. - The closer's push + `gh pr create` + review comment and the verifier's `ca upload` become fixed host-side tools (create_pull_request, comment_on_pull_request, upload_evidence); prompts updated. - Host-side git push/checkout no longer run repo hooks, which agents can write (e.g. husky's .husky/). - The interviewer session no longer gets Pi's built-in edit/write tools. Co-authored-by: mitch-fultz <mitch-fultz@users.noreply.github.com>
`ca --agent` started its Pi session without a tool allowlist, so Pi's default bash/edit/write were active alongside Case's tools. The session now gets only a path-guarded read plus run_pipeline, fetch_issue, create_task and run_baseline. Tests cover the allowlist with mocked Pi and with a real AgentSession. Co-authored-by: mitch-fultz <mitch-fultz@users.noreply.github.com>
[High impact] The PR changes command and file boundaries across the agent workflow, including authenticated PR actions. Fix the custom Pi credential exposure and video overwrite before merging. FindingsFix with agent prompt### Issue 1
src/agent/sandbox.ts:35-38
If the user stores Pi credentials in a custom `PI_CODING_AGENT_DIR`, `hiddenPaths()` does not hide that directory. The guarded `read` tool can read its `auth.json`. Isolated sessions also link that file into their temporary directories; resolving the link still leaves it outside the denied paths.
Add the configured auth location to the read restrictions before replacing `PI_CODING_AGENT_DIR` with the isolated directory.
**How this was verified:** The sessions link `realAgentDir/auth.json`, while `readablePath()` rejects only the fixed home paths and `/proc`, allowing a custom auth path.
### Issue 2
src/commands/upload.ts:102
Uploading `video.webm` now writes to the adjacent `video.mp4`. If that file already exists, `ffmpeg -y` silently overwrites it. An upload command can therefore destroy a user's existing video.
Create a unique temporary directory beside the input and put the converted file there instead.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.SummaryThe PR puts agent shell commands behind a sandbox, adds guarded file tools, and moves credentialed GitHub and upload steps into fixed host-side tools.
Acknowledged limits from
|
| function hiddenPaths(): string[] { | ||
| // Bun's homedir() ignores $HOME, which is what `~` means to the sandboxed shell; cover both. | ||
| const homes = [...new Set([process.env.HOME, homedir()].filter((home): home is string => !!home))]; | ||
| const configHome = process.env.XDG_CONFIG_HOME; |
There was a problem hiding this comment.
Custom Pi credentials remain readable
If the user stores Pi credentials in a custom PI_CODING_AGENT_DIR, hiddenPaths() does not hide that directory. The guarded read tool can read its auth.json. Isolated sessions also link that file into their temporary directories; resolving the link still leaves it outside the denied paths.
Add the configured auth location to the read restrictions before replacing PI_CODING_AGENT_DIR with the isolated directory.
How this was verified: The sessions link realAgentDir/auth.json, while readablePath() rejects only the fixed home paths and /proc, allowing a custom auth path.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent/sandbox.ts
Line: 35-38
Comment:
**Custom Pi credentials remain readable**
If the user stores Pi credentials in a custom `PI_CODING_AGENT_DIR`, `hiddenPaths()` does not hide that directory. The guarded `read` tool can read its `auth.json`. Isolated sessions also link that file into their temporary directories; resolving the link still leaves it outside the denied paths.
Add the configured auth location to the read restrictions before replacing `PI_CODING_AGENT_DIR` with the isolated directory.
**How this was verified:** The sessions link `realAgentDir/auth.json`, while `readablePath()` rejects only the fixed home paths and `/proc`, allowing a custom auth path.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| if ((await ffmpegCheck.exited) === 0) { | ||
| const stem = basename(filePath, `.${ext}`); | ||
| mp4Path = `/tmp/${stem}.mp4`; | ||
| mp4Path = join(dirname(filePath), `${stem}.mp4`); |
There was a problem hiding this comment.
Uploads overwrite existing videos
Uploading video.webm now writes to the adjacent video.mp4. If that file already exists, ffmpeg -y silently overwrites it. An upload command can therefore destroy a user's existing video.
Create a unique temporary directory beside the input and put the converted file there instead.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/upload.ts
Line: 102
Comment:
**Uploads overwrite existing videos**
Uploading `video.webm` now writes to the adjacent `video.mp4`. If that file already exists, `ffmpeg -y` silently overwrites it. An upload command can therefore destroy a user's existing video.
Create a unique temporary directory beside the input and put the converted file there instead.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Fixes VULN-1413. Agent roles ran Pi's bash with the host's credentials, environment tokens and network. A prompt injection in issue text, repo content or PR comments could therefore reach RCE and credential theft.
What changed
@anthropic-ai/sandbox-runtime(bubblewrap on Linux, sandbox-exec on macOS) through Pi's bashoperationshook. No Pi upgrade was needed.~/.ssh,~/.gnupg,~/.aws,~/.azure,~/.kube,~/.docker,~/.config/{gh,git,gcloud},~/.git-credentials,~/.gitconfig,~/.netrc,~/.npmrc,~/.yarnrc.yml,~/.pypirc,~/.pi,~/.claude, keyrings, shell rc files.CASE_*, etc.), so tokens and API keys don't reach it. Git gets an empty global config, and only the commit name and email are passed through./tmp, and the repo's git dirs. Git hooks and config stay read-only, as do Pi's.pi/and agent dirs.~/.sshor/proc/*/environ, or plant.git/hooks.create_pull_requestpushes the current branch and runsgh pr create. It refusesmain/master.comment_on_pull_requestrunsgh pr review --comment.upload_evidencerunsca uploadon a validated copy of the file.git pushandgit checkoutno longer run repo hooks. Agents can write hook paths like husky's.husky/.ca --agentis read-only. The session now gets a guardedreadplusrun_pipeline,fetch_issue,create_taskandrun_baseline, with no bash, edit or write. The interviewer session also lost Pi's built-in edit/write, which it was getting by accident.ca uploadnow writes its webm→mp4 output next to the input instead of at a predictable/tmppath.Please confirm (guessed)
registry.npmjs.org,api.workos.com,*.authkit.app, plus localhost binding. GitHub is deliberately excluded. If verification of some repo needs more domains, add them toALLOWED_DOMAINSinsrc/agent/sandbox.ts.~/.config/case/credentials) stays readable inside the sandbox, as before. These are test-app credentials, not host credentials.Testing
New
test/sandbox/suite. It runs as its own process, unmocked, as a third step ofbun run test. It runs real sandboxed bash for every role and checks that:/proc/*/environ;$HOMEand.git/hooksfail, while repo and/tmpwrites succeed;git commitworks;It also checks, with a real Pi
AgentSession, thatca --agenthas no bash, edit or write.Existing tests: the unit step has the same 46 failures as on
mainand none new. Those failures are mock leakage between test files and gitignored asset files. The standalone and sandbox steps pass.typecheckis clean. Lint and format issues are unchanged frommain.Manual checks:
ghandca. The push landed, the huskypre-pushhook did not run, shell syntax in the PR body stayed inert text, and symlink, non-media and fake-webm (HLS playlist) uploads were rejected.registry.npmjs.orgwas reachable andgithub.comwas blocked.dist/cabuilds, andca --agentfrom source starts.Known limits / follow-ups
pnpm dev &) don't survive between calls. The UI verification flow likely needs a single-call approach there./tmpare read-only. Installs that need to write to them (e.g. adding a new dependency) can fail.ca snapshotin the retrospective fails for the same reason.run_baseline(andca bootstrap) still run the repo's install and test commands host-side, unsandboxed. Repo-shipped.pi/config is still trusted by Pi inca --agentandca onboard --interview.dist/ca --agentalready crashed onmain(missing Pi theme asset). That bug is unrelated to this change.