feat(mitmproxy): wire the sidecar into the bottle launch lifecycle

Second step of PRD 0005. The mitmproxy sidecar from the previous
commit now actually runs alongside pipelock when a bottle launches.

- BottleBackend gains a non-abstract provision_ca with a default
  no-op so non-Docker backends aren't forced to implement TLS
  interception. provision() orchestrates ca → prompt → skills → ssh
  → git; CA goes first so trust is set up before anything else runs
  inside the agent.

- DockerBottlePlan gains `mitmproxy_plan: MitmproxyProxyPlan`. The
  prepare step builds it alongside the existing pipelock plan; no
  new manifest schema or host-side scratch files.

- DockerBottleBackend grows self._mitm, threads it through prepare
  and launch. Mirror of the existing self._proxy pattern.

- launch.py brings the mitmproxy sidecar up between pipelock and
  the agent container, passing pipelock's service-name URL via
  env. ExitStack callback handles teardown in reverse order.

- The agent's HTTPS_PROXY / HTTP_PROXY now point at mitmproxy (not
  pipelock directly). Three new -e flags inject the CA trust trio
  (NODE_EXTRA_CA_CERTS / SSL_CERT_FILE / REQUESTS_CA_BUNDLE) at
  docker run time; Docker propagates those into docker exec so the
  claude process sees them without per-exec threading.

- New provisioner backend/docker/provision/ca.py extracts the CA
  cert from the running mitmproxy sidecar, copies it into the agent
  at /usr/local/share/ca-certificates/claude-bottle-mitm.crt, runs
  update-ca-certificates, and emits a stderr line with the SHA-256
  fingerprint (stdlib ssl + hashlib; no subprocess).

Cleanup needs no change — `docker ps --filter name=^claude-bottle-`
already catches the new claude-bottle-mitm-<slug> containers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-12 13:38:51 -04:00
parent e579c3d4fd
commit 21054212d4
6 changed files with 128 additions and 14 deletions
+4
View File
@@ -19,6 +19,7 @@ from ...log import die
from .. import BottleSpec
from . import util as docker_mod
from .bottle_plan import DockerBottlePlan
from .mitmproxy import DockerMitmproxyProxy
from .pipelock import DockerPipelockProxy
@@ -27,6 +28,7 @@ def resolve_plan(
*,
stage_dir: Path,
proxy: DockerPipelockProxy,
mitm: DockerMitmproxyProxy,
) -> DockerBottlePlan:
"""Resolve Docker-specific names and write scratch files. Trusts
that the agent and its skills/SSH keys are present — validation
@@ -78,6 +80,7 @@ def resolve_plan(
prompt_file.chmod(0o600)
proxy_plan = proxy.prepare(bottle, slug, stage_dir)
mitmproxy_plan = mitm.prepare(slug)
resolved = resolve_env(manifest, spec.agent_name)
# Everything that should reach the bottle by-name (so its value
# never lands on argv or in env_file) goes into one dict. The
@@ -105,6 +108,7 @@ def resolve_plan(
forwarded_env=forwarded_env,
prompt_file=prompt_file,
proxy_plan=proxy_plan,
mitmproxy_plan=mitmproxy_plan,
allowlist_summary=allowlist_summary,
use_runsc=use_runsc,
)