feat(egress-proxy): cutover from cred-proxy (PRD 0017 chunk 2) #29
Reference in New Issue
Block a user
Delete Branch "egress-proxy-cutover"
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
Chunk 2 of PRD 0017 (docs/prds/0017-egress-proxy-via-mitmproxy.md). Hard cutover: cred-proxy is gone, egress-proxy is the agent's HTTP_PROXY. PR #28 (chunk 1) shipped the artifact alongside cred-proxy; this PR makes the switch.
Net –3,242 LOC. 355 unit + 24 integration tests pass (was 427 pre-cutover; cred-proxy-specific tests removed).
Includes one follow-up commit on top of the cutover: universal HTTPS git-push block in the addon (
is_git_push_requestinegress_proxy_addon_core, called from the mitmproxy request hook before route matching). 403s any/git-receive-packorinfo/refs?service=git-receive-pack— defense-in-depth so git-gate (PRD 0008) remains the only sanctioned outbound path for git writes, gitleaks-scanned by its pre-receive. Replicates cred-proxy'sis_git_push_requestbehavior.What changes for users
bottle.cred_proxy→ hard error with a migration recipe pointing at PRD 0017 and showing each route field's new home (path+upstream→host, flatauth_scheme+token_ref→ nestedauth: { scheme, token_ref },path_allowlistis new,roledropped).egress_proxy.routes[]now run mitmproxy as their HTTP_PROXY; egress-proxy enforcespath_allowlist, injects auth, blocks HTTPS git-push, and forwards through pipelock (which keeps the egress allowlist + DLP body scan on the upstream leg). Bottles with no egress_proxy routes are unchanged — they still talk straight to pipelock.Topology
Two per-bottle CAs:
egress_proxy_tls_init(reuses pipelock'stls initsubcommand; concatenates cert+key into mitmproxy's PEM format). Installed in the agent's trust store viaprovision_ca(selects egress-proxy CA over pipelock CA when egress_proxy routes are declared).--set ssl_verify_upstream_trusted_ca=$EGRESS_PROXY_UPSTREAM_CA(Dockerfile entrypoint conditionally appends the flag based on the env var, so standalone runs without a mounted pipelock CA still boot).Code-level
claude_bottle/{cred_proxy,cred_proxy_server}.py,backend/docker/{cred_proxy,provision/cred_proxy}.py,Dockerfile.cred-proxy— gone.backend/docker/cred_proxy_apply.py— kept as a stub for chunk 3 to rewrite; the two constants it pulled from the deleted module are inlined.manifest.py—CredProxyRoute/CredProxyConfig/role validators removed;cred_proxykey hard-fails; egress_proxy stays (added in chunk 1).launch.py—egress_proxy_tls_initruns alongsidepipelock_tls_init; egress-proxy sidecar wiring replaces cred-proxy. Agent's HTTP_PROXY is_agent_proxy_url(plan)(egress-proxy when routes exist, else pipelock).prepare.py—egress_proxy: DockerEgressProxyparameter; sidecar-orphan probe + plan field + dashboard view renamed. The cred-proxyanthropic-base-urlrole/dance is replaced by a simple check: when any egress_proxy route usestoken_ref="CLAUDE_CODE_OAUTH_TOKEN", set the placeholder + telemetry-off envs.pipelock.py—pipelock_token_hosts→pipelock_route_hosts; the cred-proxy hostname auto-allow is replaced by an egress-proxy auto-allow; the seed-phrase-detection workaround now triggers onhost == api.anthropic.comroutes.egress_proxy_addon_core.py—is_git_push_requestadded; addon hook 403s git-receive-pack regardless of route.bottle.provision— drops the cred-proxy dotfile-rewrite step entirely. The agent-side ~/.npmrc / tea-config / git-insteadof rewrites had no clear function in the egress-proxy world (HTTP_PROXY catches everything respecting it, and the onlyrolehost values matched the tools' built-in defaults). If a future bottle needs a non-default npm registry or tea login, we'll ship a more direct mechanism then.Validated locally
python3 -m unittest discover -s tests -t .→ 379 pass (1 skipped, environment-dependent).docker build -f Dockerfile.egress-proxy .succeeds.python3 -c "import claude_bottle.cli.dashboard, claude_bottle.backend.docker.backend, claude_bottle.cli.start"clean.What's left for chunk 3
cred-proxy-blockMCP tool at egress-proxy (rename or keep the ID; rewritecred_proxy_apply.pyto docker-exec into egress-proxy + SIGHUP it on apply).test_supervise_sidecar.py(this PR temporarily flipped it to a reject path because the approval path hits a deleted sidecar).Hard cutover. cred-proxy is deleted; egress-proxy is now the agent's HTTP_PROXY (when routes are declared) with pipelock on its outbound leg. Two per-bottle CAs are minted: egress-proxy's (agent trust store) and pipelock's (egress-proxy's outbound trust store). Manifest: - `bottle.cred_proxy` → hard error with a migration recipe. - `bottle.egress_proxy` is the new shape (PRD 0017 chunk 1). - CredProxy* types + role validators removed. Wiring: - launch.py: `egress_proxy_tls_init` mints the egress-proxy CA (cert+key concat for mitmproxy + cert-only for agent trust); `DockerEgressProxy.start` docker-cps both CAs in, sets `HTTPS_PROXY=pipelock` + `EGRESS_PROXY_UPSTREAM_CA` so mitmdump trusts pipelock's MITM. Agent's HTTP_PROXY points at egress-proxy when routes exist, else falls back to pipelock (no-routes bottles unchanged). - prepare.py / backend.py: `cred_proxy` arg → `egress_proxy`; sidecar-orphan probe + plan field + dashboard view all renamed. - provision_ca: selects the egress-proxy CA when present, else pipelock's (filename renamed to claude-bottle-mitm-ca.crt). - bottle.provision: cred-proxy dotfile rewrites (~/.npmrc, ~/.gitconfig insteadOf, tea config) are gone — HTTP_PROXY catches everything respecting it. Pipelock helpers: - `pipelock_token_hosts` → `pipelock_route_hosts` (now reading egress_proxy.routes). - cred-proxy hostname auto-allow → egress-proxy hostname auto-allow. - Anthropic seed-phrase workaround now triggers when an egress_proxy route targets api.anthropic.com (was based on the cred-proxy `anthropic-base-url` role). Dockerfile.egress-proxy: - Entrypoint conditionally passes `--set ssl_verify_upstream_trusted_ca=$EGRESS_PROXY_UPSTREAM_CA` (via the `${VAR:+...}` shell expansion) so standalone runs without a mounted pipelock CA still boot. - mkdirs `/home/mitmproxy/.mitmproxy` ahead of `docker cp`. Deleted: claude_bottle/{cred_proxy,cred_proxy_server}.py, backend/docker/{cred_proxy,provision/cred_proxy}.py, Dockerfile.cred-proxy, plus the corresponding unit + integration tests. backend/docker/cred_proxy_apply.py stays as a stub for chunk 3 to rewrite (its container-name + routes-path constants are inlined so it survives without the deleted module). Test changes: - test_pipelock_allowlist rewritten against egress-proxy routes + the new `pipelock_route_hosts`. - test_manifest_md_load + test_pipelock_yaml + test_yaml_subset fixtures migrated to the `egress_proxy: { routes: [...] }` shape. - test_supervise_sidecar's round-trip test switched from `dashboard.approve` to `dashboard.reject`: the approval-apply path on cred-proxy-block proposals hits a deleted sidecar in chunk 2's transitional state. Chunk 3 restores the approval test once the remediation flow is retargeted at egress-proxy. 376 tests pass (was 427; net delta is removed cred-proxy tests). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>