feat(manifest)!: enforce cwd-manifest trust boundary (PRD 0011)
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.
This commit is contained in:
@@ -186,9 +186,28 @@ left running; remove it with `docker rm -f <container-name>`.
|
||||
|
||||
## Manifest
|
||||
|
||||
Agents and the bottles they run in are declared in `claude-bottle.json`
|
||||
in your project root or `$HOME` (both files merge if present, with
|
||||
project entries overriding home entries on key conflict).
|
||||
Two locations, two roles (PRD 0011):
|
||||
|
||||
- **`$HOME/claude-bottle.json`** — owns *bottles*. Defines the
|
||||
per-bottle infrastructure: `cred_proxy.routes` (which API tokens
|
||||
the bottle holds, pointing at host env vars), `git` (SSH upstreams
|
||||
+ identity files), `env` (literal/interpolated values), `egress`
|
||||
(pipelock's allowlist + DLP action). This is your trusted file.
|
||||
- **`$CWD/claude-bottle.json`** *(optional)* — declares *agents*
|
||||
that reference home-defined bottles. Useful for shipping a
|
||||
repo-specific prompt or skill list with a project. A cwd manifest
|
||||
that tries to define `bottles:` dies at parse with a pointer at
|
||||
this rule.
|
||||
|
||||
The boundary is intentional: a cloned repo's `claude-bottle.json`
|
||||
cannot redirect your `CLAUDE_BOTTLE_OAUTH_TOKEN` / `GITHUB_TOKEN`
|
||||
to an attacker-named upstream, because it cannot define
|
||||
infrastructure — only your home file can, and it lives on your
|
||||
own machine. The y/N preflight prints
|
||||
`bottle: <name> (from $HOME/claude-bottle.json)` as a positive
|
||||
audit signal.
|
||||
|
||||
The full schema lives in `$HOME`:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
@@ -264,9 +283,29 @@ project entries overriding home entries on key conflict).
|
||||
```
|
||||
|
||||
Comments are illustrative; the file itself must be valid JSON. See
|
||||
`claude-bottle.example.json` for a working starting point. Pipelock's
|
||||
design lives in `docs/prds/0001-per-agent-egress-proxy-via-pipelock.md`
|
||||
and the rationale in `docs/research/pipelock-assessment.md`.
|
||||
`claude-bottle.example.json` for a working starting point — copy it
|
||||
to `$HOME/claude-bottle.json`, not into the repo you're working in.
|
||||
A repo can add its own `claude-bottle.json` that declares only
|
||||
`agents:`, each referencing a bottle name from `$HOME`:
|
||||
|
||||
```jsonc
|
||||
// $CWD/claude-bottle.json — repo-shipped agents, no bottles
|
||||
{
|
||||
"agents": {
|
||||
"implementer": {
|
||||
"bottle": "gitea-dev",
|
||||
"skills": ["init-prd"],
|
||||
"prompt": "Implement features against this repo's PRDs..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Pipelock's design lives in
|
||||
`docs/prds/0001-per-agent-egress-proxy-via-pipelock.md` and the
|
||||
rationale in `docs/research/pipelock-assessment.md`. The trust
|
||||
boundary rationale lives in
|
||||
`docs/prds/0011-cwd-manifest-trust-boundary.md`.
|
||||
|
||||
## Auth: OAuth token, not API key
|
||||
|
||||
|
||||
Reference in New Issue
Block a user