Commit Graph

6 Commits

Author SHA1 Message Date
didericis 8582e608af fix(ssh): tunnel ssh through pipelock so agents on --internal can reach git remotes
The agent container is on an --internal Docker network with no default
route — only the pipelock sidecar is reachable. HTTPS_PROXY routes
HTTP through pipelock, but raw TCP (e.g. SSH on port 30009) had no
egress path, so `git fetch` against any bottle.ssh entry failed with
"Network is unreachable".

Fix: tunnel SSH through pipelock's HTTP CONNECT proxy.
- lib/ssh.sh injects `ProxyCommand socat - PROXY:<pipelock>:%h:%p,proxyport=<n>`
  into each Host block in the in-container ~/.ssh/config. socat is
  already in the image (apt-installed for the ssh-agent forwarder).
- lib/pipelock.sh auto-adds each bottle.ssh[].Hostname to the effective
  allowlist so pipelock permits the CONNECT.
- cli.sh threads the pipelock host:port into ssh_setup.

Note: works for SSH hosts pipelock's SSRF layer doesn't block. CGNAT
(100.64.0.0/10) and other non-RFC1918 ranges should pass; if a future
host gets blocked, expose pipelock's trusted_domains as a follow-up.

Assisted-by: Claude Code
2026-05-08 01:39:08 -04:00
didericis 713424214e fix(cli): install cleanup_all trap before pipelock_start to avoid orphan networks
Previously cleanup_all was defined AFTER network_create_internal /
network_create_egress / pipelock_start ran, so a failure during
pipelock_start (or in network_create_egress added by the prior commit)
would land in the cleanup_stage trap that knows nothing about networks.
The internal and egress networks would survive the failed launch and
accumulate as orphans on the host.

Move the cleanup_all definition + `trap … EXIT INT TERM` install ahead
of the resource creation, and gate the CONTAINER branch on
`-n "${CONTAINER:-}"` since CONTAINER is set earlier in the function
but the trap now runs in the early-failure window. pipelock_stop and
network_remove are already idempotent against missing resources.

Smoke test: with `CLAUDE_BOTTLE_PIPELOCK_IMAGE` pinned to a nonexistent
digest, `./cli.sh start implementer` now creates both networks, fails
at pipelock_start, and exits with both networks removed —
`docker network ls | grep claude-bottle` returns nothing.

Assisted-by: Claude Code
2026-05-08 01:18:40 -04:00
didericis 55bb230969 fix(network): create user-defined egress bridge for pipelock sidecar
Docker's legacy `bridge` network has no embedded DNS resolver — only
user-defined bridges do — so attaching the pipelock sidecar to `bridge`
made it unable to resolve `api.anthropic.com` and dead-ended Claude Code
traffic. Add `network_create_egress`, refactored around a shared
`_network_create_with_prefix` helper, and wire it through `pipelock_start`
and `cli.sh` so the sidecar straddles the agent's --internal network and
a per-agent user-defined egress bridge instead. The agent container
itself still attaches to the internal network only.

Assisted-by: Claude Code
2026-05-08 01:16:46 -04:00
didericis e7e72c4833 feat(cli): wire pipelock sidecar + internal network into start flow
PRD 0001 cli.sh integration:

- Source the new lib/network.sh and lib/pipelock.sh.
- During plan resolution: generate the per-bottle pipelock YAML into
  the existing mktemp stage dir (mode 600, hostnames only) and
  resolve a one-line "<N> hosts allowed (...)" summary.
- Add the egress summary as a sub-bullet under the bottle in the y/N
  preflight, alongside the existing ssh hosts line.
- After the y/N gate (and after build_image): create the per-agent
  --internal Docker network with a slug-derived name, then start the
  pipelock sidecar attached to it.
- docker run argv: agent attaches to the internal network with
  HTTPS_PROXY / HTTP_PROXY pointing at the sidecar by service name on
  that network. NO_PROXY only covers loopback. The internal network
  has no default gateway, so any path that ignores the proxy env
  hits no-route-to-host rather than leaking.
- Exit trap: tear down the agent container, then the sidecar (so the
  network is empty), then remove the network, then run the existing
  stage cleanup. Order matters — docker refuses to remove a network
  with attached containers.
- --dry-run continues to exit before any docker network/run/cp/exec
  call; the YAML write into the mktemp dir is the only new
  side-effect inside the dry-run path.

Verified against a temp fixture: defaults-only bottle shows
"7 hosts allowed", a bottle with two extra entries shows
"9 hosts allowed (api.anthropic.com, api.openai.com, claude.ai,
+6 more)", and dry-run exits before any docker calls.

Refs: docs/prds/0001-per-agent-egress-proxy-via-pipelock.md

Assisted-by: Claude Code
2026-05-08 01:01:20 -04:00
didericis 74a2c7a32a refactor: rename box/boxes to bottle/bottles in config schema and code
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 23:02:34 -04:00
didericis c45f384fb8 Initial commit 2026-05-07 22:45:36 -04:00