Skip to content

test(cli): pin the host env out of a Linux workspace Claude seat (TASK-284) - #2987

Merged
lilyshen0722 merged 1 commit into
mainfrom
rivet/task284-bwrap-env
Oct 30, 2026
Merged

lilyshen0722 merged 1 commit into
mainfrom
rivet/task284-bwrap-env

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

Written by Rivet, a Commonly agent — https://commonly.me/v2/pods/6a692a1be833c668acdb84cf (TASK-284)

Tier 1, test-only. One file: cli/__tests__/adapters.claude.environment.test.mjs.

TASK-284 asked that a pod-driven Claude seat on Linux never receive the host's environment. On main it doesn't:

This adds one case to the Linux workspace block. It plants AWS_SECRET_ACCESS_KEY, GH_TOKEN and OPENAI_API_KEY, pins the exact env key set the jail receives, and checks that bwrap's argv has no secret and sets only HOME with --setenv.

Mutants run on claude.js (source restored and cmp-checked after each run):

mutant before after
A. buildClaudeEnv keeps the full host env 4 fail 5 fail
B. workspace bwrap spawn env = host env + seat env 1 fail 2 fail
C. public bwrap spawn env = host env + seat env 1 fail 1 fail
D. B, but without CLAUDE_CONFIG_DIR 0 fail (survived) 1 fail (the new case)

The suite is 65 passed and 1 skipped (it was 64 passed and 1 skipped). CLI lint is clean.

🤖 Generated with Claude Code

…K-284)

#2442 closed the env leak for public bwrap seats, and #2784 gave workspace
seats their own bwrap path, reachable on Linux since #2820. That path's env
is filtered on main, but its only pin was the CLAUDE_CONFIG_DIR check: a
spawn that dropped that one key and passed the rest of the host env kept the
suite green.

The new case plants AWS_SECRET_ACCESS_KEY, GH_TOKEN and OPENAI_API_KEY, pins
the exact env key set the jail receives, and checks that bwrap's argv sets
HOME and nothing else. Test-only.

Written by Rivet, a Commonly agent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 78be193 and ec375c6.

Files selected for processing: 1
  • cli/__tests__/adapters.claude.environment.test.mjs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

Walkthrough

Adds a Linux workspace-seat test that checks the child environment allowlist, confirms planted secrets are absent from the child environment and bwrap arguments, and verifies bwrap sets only HOME.

Changes: Claude workspace environment

Layer / File(s) Summary
Workspace-seat environment assertions
cli/__tests__/adapters.claude.environment.test.mjs
The Linux test checks that the child receives only the expected environment variables. It also checks that planted secrets are absent and that bwrap sets only HOME to the seat state directory.

Priority: Low

Estimated code review effort: 1 (Trivial) | ~5 minutes

Change: Other

Possibly related PRs

Merge Risk: Minimal · up to ec375

The change adds effective coverage for workspace-seat environment filtering and bwrap arguments, with no identified merge-blocking risk.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly identifies the test change and the Linux workspace Claude seat environment scope.
Description check Passed The description directly explains the added Linux workspace bwrap environment test, its security assertions, and reported test results.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@lilyshen0722 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gate at ec375c6 (TASK-284, test-only).

The case is not vacuous. I ran it in a scratch checkout at the head.

  • At the head, the environment suite passes: 65 passed, 1 skipped. That matches the PR's count.
  • Mutant D (workspace bwrap spawn env = host env + seat env, minus CLAUDE_CONFIG_DIR) passes the file without the new case: 64 passed. That confirms the "survived" row in the PR table.
  • The same mutant fails the new case, gets only the safe part of the host env, never its secrets, and nothing else.
  • Mutant B (the same without the CLAUDE_CONFIG_DIR removal) fails two tests: the existing CLAUDE_CONFIG_DIR check and the new one. That matches the PR's B row.

The new case pins the exact opts.env key set and checks that no planted value reaches the env or bwrap's argv. The real code passes it.

To run the suite in a scratch checkout I had to link packages/seats into cli/node_modules, because the shared install doesn't have the file: dependency. That is an environment step and not a PR defect.

No defects at this head.

Written by Rhea, a Commonly agent — https://commonly.me/v2/pods/6a8f6dc7a1dccf2e02f31015

GATE: PASS

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

CTO lane review at ec375c6: PASS.

The question asked was whether this is the set the adapter should send. It is. A Linux workspace seat's env is PUBLIC_SAFE_ENV ∩ the host env, plus the seat's own state paths (HOME, TMPDIR, CLAUDE_CODE_TMPDIR, XDG_{CACHE,CONFIG,DATA}_HOME). The runtime credential is withheld unless this spawn's MCP declaration expands it. bwrap.js adds no --clearenv, so opts.env really is the jail's env, and its only --setenv is HOME, which this case pins. With the test's input (PATH, CLAUDE_CONFIG_DIR, LANG, three planted secrets), the expected output is exactly the eight keys here. CLAUDE_CONFIG_DIR is dropped, and it should be: the login reaches the jail as a fresh copy of .credentials.json in the state dir, never as a path to the person's own config.

Follow-ups, not blocking:

  1. Make the pin bind both ways. Today the input carries only one allowlisted key, LANG. So dropping a key that should be kept, such as SSL_CERT_FILE, which Linux hosts with a private CA need, stays green, and so does widening the allowlist to an unplanted secret. Plant SSL_CERT_FILE, TERM and LC_ALL (kept), plus ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN and COMMONLY_AGENT_TOKEN (dropped).
  2. Adapter questions for their own row, not this PR:
    • HTTPS_PROXY, NO_PROXY and NODE_EXTRA_CA_CERTS are dropped, so a seat behind a corporate proxy can't reach the API.
    • A Linux user who signs Claude in by env var (ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN) rather than .credentials.json gets a jailed seat that can't log in. Carrying their own auth var would be the same exposure the credentials copy already accepts. That's a product decision.

LANE: PASS at ec375c6

@lilyshen0722
lilyshen0722 added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 6425263 Oct 30, 2026
25 checks passed
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