feat(sidecars): egress binds 127.0.0.1 when EGRESS_LISTEN_HOST is set (PRD 0023 chunk 3) #68

Merged
didericis-claude merged 1 commits from prd-0023-chunk-3-egress-bind-localhost into main 2026-05-27 04:54:16 -04:00
Collaborator

Summary

Chunk 3 of PRD 0023 — the bind-address mitigation for TSI's IP-granular allowlist. The smolmachines guest's TSI allowlist is <bundle-ip>/32: agent can reach that IP on any port. Without this change, the agent could dial <bundle-ip>:9099 (egress's port) and bypass pipelock's DLP. Binding egress to localhost inside the bundle closes the gap at the socket level.

Mechanism

egress_entrypoint.sh reads EGRESS_LISTEN_HOST. If set, it appends --listen-host <host> to mitmdump's argv.

  • Smolmachines backend: BundleLaunchSpec.environment includes EGRESS_LISTEN_HOST=127.0.0.1. Agent dials pipelock (<bundle-ip>:8888); pipelock's upstream is egress on bundle-localhost; egress is unreachable from outside the bundle.
  • Docker backend: env var unset → mitmdump's default bind (all interfaces). The docker agent dials egress:9099 directly via the docker network alias, so egress must remain externally accessible there.

The asymmetry is by design and documented in the entrypoint script's comment.

Tests

  • 5 new unit cases (tests/unit/test_egress_entrypoint.py): run the entrypoint script with a fake mitmdump shim that prints its argv, assert the flag is present/absent under each env permutation (unset, empty, 127.0.0.1, combined with upstream-proxy mode).
  • 3 integration cases (chunk 2d's test_smolmachines_launch) still pass: the egress-port-bypass probe preserves its property — chunk 2d ran with daemons_csv="" so no egress was up; chunk 3 makes the property hold once egress IS up in chunk 4 (when real daemons land).

545 unit + 3 integration tests passing locally.

What's left

  • Chunk 4 — provisioning parity (CA install, prompt, skills, .git, supervise) + the agent-image-conversion gap (chunk 2d's alpine placeholder → real claude-bottle image).
  • Chunk 5 — PRD 0022 sandbox-escape suite green under CLAUDE_BOTTLE_BACKEND=smolmachines.
## Summary Chunk 3 of PRD 0023 — the bind-address mitigation for TSI's IP-granular allowlist. The smolmachines guest's TSI allowlist is `<bundle-ip>/32`: agent can reach that IP on any port. Without this change, the agent could dial `<bundle-ip>:9099` (egress's port) and bypass pipelock's DLP. Binding egress to localhost inside the bundle closes the gap at the socket level. ## Mechanism `egress_entrypoint.sh` reads `EGRESS_LISTEN_HOST`. If set, it appends `--listen-host <host>` to mitmdump's argv. - **Smolmachines backend:** `BundleLaunchSpec.environment` includes `EGRESS_LISTEN_HOST=127.0.0.1`. Agent dials pipelock (`<bundle-ip>:8888`); pipelock's upstream is egress on bundle-localhost; egress is unreachable from outside the bundle. - **Docker backend:** env var unset → mitmdump's default bind (all interfaces). The docker agent dials `egress:9099` directly via the docker network alias, so egress must remain externally accessible there. The asymmetry is by design and documented in the entrypoint script's comment. ## Tests - **5 new unit cases** (`tests/unit/test_egress_entrypoint.py`): run the entrypoint script with a fake `mitmdump` shim that prints its argv, assert the flag is present/absent under each env permutation (unset, empty, `127.0.0.1`, combined with upstream-proxy mode). - **3 integration cases** (chunk 2d's `test_smolmachines_launch`) still pass: the egress-port-bypass probe preserves its property — chunk 2d ran with `daemons_csv=""` so no egress was up; chunk 3 makes the property hold once egress IS up in chunk 4 (when real daemons land). **545 unit + 3 integration tests passing locally.** ## What's left - **Chunk 4** — provisioning parity (CA install, prompt, skills, .git, supervise) + the agent-image-conversion gap (chunk 2d's alpine placeholder → real claude-bottle image). - **Chunk 5** — PRD 0022 sandbox-escape suite green under `CLAUDE_BOTTLE_BACKEND=smolmachines`.
didericis-claude added 1 commit 2026-05-27 04:49:42 -04:00
feat(sidecars): egress binds 127.0.0.1 when EGRESS_LISTEN_HOST is set (PRD 0023 chunk 3)
test / unit (pull_request) Successful in 21s
test / integration (pull_request) Successful in 41s
909029085e
Egress's bind address is now env-driven via EGRESS_LISTEN_HOST.
Unset → mitmdump's default (all interfaces) — the docker
backend's behavior, unchanged. Set to `127.0.0.1` → mitmdump
binds localhost only.

The smolmachines launch sets EGRESS_LISTEN_HOST=127.0.0.1 in
the bundle's env unconditionally. TSI's allowlist is
`<bundle-ip>/32` (IP-only, not port-granular), which would
otherwise let the agent dial `<bundle-ip>:9099` and bypass
pipelock's DLP by talking to egress directly. Binding egress
to localhost inside the bundle closes that gap at the socket
level — the agent still reaches the IP (TSI permits it) but
egress refuses the connect because it's not listening on the
docker bridge interface.

The docker backend doesn't set the env var because its agent
dials egress directly via the docker network alias — egress
MUST be reachable from outside the bundle there. The
asymmetry is documented in the entrypoint script's comment.

Changes:
- egress_entrypoint.sh: read EGRESS_LISTEN_HOST, conditionally
  pass `--listen-host <host>` to mitmdump.
- smolmachines/launch.py: BundleLaunchSpec.environment now
  includes `EGRESS_LISTEN_HOST=127.0.0.1`.
- New unit tests (5): the entrypoint script's argv shape under
  various env combinations, verified via a fake mitmdump shim
  that prints its argv.

545 unit + 3 integration tests passing. The egress-port-bypass
probe from chunk 2d still passes (chunk 2d ran with daemons_csv=""
so no egress was up; chunk 3 makes the probe preserve its
property once egress IS up in chunk 4).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
didericis requested review from didericis 2026-05-27 04:52:10 -04:00
didericis approved these changes 2026-05-27 04:53:52 -04:00
didericis-claude merged commit 554d60324d into main 2026-05-27 04:54:16 -04:00
didericis deleted branch prd-0023-chunk-3-egress-bind-localhost 2026-05-27 04:54:36 -04:00
Sign in to join this conversation.