PRD 0011: Trust boundary for cwd-supplied manifests #15
Reference in New Issue
Block a user
Delete Branch "cwd-manifest-trust"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Manifest.resolvedeep-merges$CWD/claude-bottle.jsonwith$HOME/claude-bottle.json, cwd entries overriding home on key conflict. A repo'sclaude-bottle.jsoncan therefore redefine a bottle'scred_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 redirectCLAUDE_BOTTLE_OAUTH_TOKENorGITHUB_TOKENto 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).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.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
CwdExtensionparser). 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.Pull request closed