PRD 0011: Trust boundary for cwd-supplied manifests #15

Closed
didericis wants to merge 2 commits from cwd-manifest-trust into main
Owner

Summary

Manifest.resolve deep-merges $CWD/claude-bottle.json with $HOME/claude-bottle.json, cwd entries overriding home on key conflict. A repo's claude-bottle.json can therefore redefine a bottle's cred_proxy.routes / git / env / egress.allowlist, and the launcher will faithfully read the user's host env vars and forward them into the cred-proxy sidecar — letting a cloned repo redirect CLAUDE_BOTTLE_OAUTH_TOKEN or GITHUB_TOKEN to an attacker-named upstream on first launch. No agent compromise required; the leak fires before the agent does anything.

This PRD draws the trust boundary at the bottle level: $HOME owns bottle definitions; the cwd manifest can declare agents that reference home-defined bottles (and override their prompt/skills), but cannot define or modify bottles themselves.

Six success criteria are listed in the PRD. The breaking change is intentional — existing cwd manifests that define bottles will fail at parse with a clear pointer at the trust-boundary rule.

PRD-only PR; no implementation in this commit.

Sister docs: docs/prds/0010-cred-proxy.md (the credential broker this PRD scopes around).

## Summary `Manifest.resolve` deep-merges `$CWD/claude-bottle.json` with `$HOME/claude-bottle.json`, cwd entries overriding home on key conflict. A repo's `claude-bottle.json` can therefore redefine a bottle's `cred_proxy.routes` / `git` / `env` / `egress.allowlist`, and the launcher will faithfully read the user's host env vars and forward them into the cred-proxy sidecar — letting a cloned repo redirect `CLAUDE_BOTTLE_OAUTH_TOKEN` or `GITHUB_TOKEN` to an attacker-named upstream on first launch. No agent compromise required; the leak fires before the agent does anything. This PRD draws the trust boundary at the bottle level: $HOME owns bottle definitions; the cwd manifest can declare agents that reference home-defined bottles (and override their prompt/skills), but cannot define or modify bottles themselves. Six success criteria are listed in the PRD. The breaking change is intentional — existing cwd manifests that define bottles will fail at parse with a clear pointer at the trust-boundary rule. PRD-only PR; no implementation in this commit. Sister docs: `docs/prds/0010-cred-proxy.md` (the credential broker this PRD scopes around).
didericis added 1 commit 2026-05-24 14:59:31 -04:00
docs: add PRD 0011 for cwd-manifest trust boundary
test / unit (pull_request) Successful in 12s
test / integration (pull_request) Successful in 23s
579a9dae3e
Bottles defined in $CWD/claude-bottle.json can redefine
cred_proxy.routes / git / env / egress on key conflict, which
gives a cloned repo's manifest the ability to redirect a host
env var (CLAUDE_BOTTLE_OAUTH_TOKEN, GITHUB_TOKEN, ...) to an
attacker-controlled upstream on first launch — no agent
compromise required.

This PRD proposes drawing the trust boundary at the bottle
level: $HOME owns bottle definitions; $CWD can only declare
agents that reference home-defined bottles. Six success
criteria + the resolver-split design.

PRD-only; no code in this commit.
didericis added 1 commit 2026-05-24 15:23:01 -04:00
feat(manifest)!: enforce cwd-manifest trust boundary (PRD 0011)
test / unit (pull_request) Successful in 13s
test / integration (pull_request) Successful in 22s
ccfdb141dd
Splits `Manifest.resolve` into a two-phase load:

1. $HOME/claude-bottle.json parses under the full schema as today.
   This file owns bottle infrastructure (cred_proxy.routes, git,
   env, egress).
2. $CWD/claude-bottle.json parses under the new CwdExtension schema
   — agents-only. Any `bottles:` section dies at parse with a
   pointer at the home file. Each cwd agent's `bottle:` must
   resolve against a home-defined bottle name.

When CWD == HOME (running from $HOME directly) the resolver
short-circuits to home-only — no false trust-boundary error from
parsing the same file twice.

Closes the exfil vector documented in PRD 0011: a cloned repo's
claude-bottle.json can no longer redefine cred-proxy routes, and
therefore can't redirect $CLAUDE_BOTTLE_OAUTH_TOKEN /
$GITHUB_TOKEN / etc. to an attacker-named upstream on first
launch.

Preflight surfaces the boundary positively: print() shows
`bottle: <name>  (from $HOME/claude-bottle.json)`, and to_dict
emits `"bottle_source": "home"`. README + the existing dry-run
integration test pick that up.

BREAKING: existing cwd manifests that define bottles now fail.
The error message names the file path, the offending field, and
the fix ("move bottles section to $HOME/claude-bottle.json").

Tests:
- tests/unit/test_manifest_trust_boundary.py — 10 cases covering
  PRD 0011's success criteria (bottles rejected, agents allowed,
  cwd overrides agent fields, no silent fallback, home-only
  unchanged, etc.).
- tests/integration/test_dry_run_plan.py picks up the
  bottle_source assertion.
Author
Owner

Closing in favor of PR #16. The implementation in this PR works — the resolver checks the cwd manifest at parse time and dies if it tries to define bottles — but the rule lives in code (the CwdExtension parser). The follow-up research doc in PR #16 makes the case that a per-file manifest reorg expresses the same trust boundary as filesystem layout ($HOME/.claude-bottle/bottles/ is the only place bottles can come from; $CWD/.claude-bottle/agents/ is the only thing cwd contributes), which is self-documenting for security reviewers and scales better to N bottles + N agents than the single-file shape this PR preserves. The grouping change is the bigger UX win; the format change (MD with YAML frontmatter for both, agents close to Claude Code's subagent spec) is what enables it. A follow-up PRD will spec the reorg and supersede PRD 0011's scope.

Closing in favor of PR #16. The implementation in this PR works — the resolver checks the cwd manifest at parse time and dies if it tries to define bottles — but the rule lives in code (the `CwdExtension` parser). The follow-up research doc in PR #16 makes the case that a per-file manifest reorg expresses the same trust boundary as filesystem layout (`$HOME/.claude-bottle/bottles/` is the only place bottles can come from; `$CWD/.claude-bottle/agents/` is the only thing cwd contributes), which is self-documenting for security reviewers and scales better to N bottles + N agents than the single-file shape this PR preserves. The grouping change is the bigger UX win; the format change (MD with YAML frontmatter for both, agents close to Claude Code's subagent spec) is what enables it. A follow-up PRD will spec the reorg and supersede PRD 0011's scope.
didericis closed this pull request 2026-05-24 21:13:33 -04:00
Some checks are pending
test / unit (pull_request) Successful in 13s
test / integration (pull_request) Successful in 22s

Pull request closed

Sign in to join this conversation.