fix(proxy): enforce sandbox — proxy_net internal, cf-proxy dual-homed
Some checks failed
CI / lint (push) Successful in 36s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / mc (push) Has been skipped
Deploy / report (push) Successful in 18s
CI / test (push) Failing after 12m53s

Before this change HTTPS_PROXY was a polite recommendation, not a
boundary: proxy_net was a regular bridge with NAT, so anything inside
the claude container could open a direct socket to the internet and
silently bypass squid's allowlist.

Now proxy_net is internal (no NAT, no outbound). cf-proxy joins a
second bridge egress_net to dial the Cloudflare edge. claude stays on
proxy_net only, so its only route off-host is through cf-proxy →
CF Access → tunnel → squid → allowlisted CONNECTs.

Verified: from a sibling container on proxy_net, curl https://1.1.1.1
fails immediately ("Could not connect to server"); the same container
through cf-proxy gets HTTP 401 from api.anthropic.com (the smoke-test
success code).
This commit is contained in:
kert
2026-05-19 10:31:05 -04:00
parent 794edf884d
commit 2bdbdc11b4

View File

@@ -1,12 +1,20 @@
# Run Claude Code through proxy.fhirworx.io from inside containers.
# Run Claude Code in a sandboxed container whose only outside path is the
# allowlisted forward proxy at proxy.fhirworx.io.
#
# Two services on a self-contained 192.168.10.0/24 bridge:
# cf-proxy cloudflared access TCP shim — terminates the Cloudflare tunnel
# on 192.168.10.2:18443. Equivalent to the systemd --user unit
# that dev/scripts/setup-proxy-client.sh installs on a WSL host.
# claude Claude Code CLI. Reaches cloudflared via the bridge; extra_hosts
# pins proxy.fhirworx.io -> 192.168.10.2 so TLS SNI/cert match
# without poisoning cf-proxy's own DNS view.
# Network topology (the point of this compose):
# proxy_net internal bridge, 192.168.10.0/24. No NAT, no internet egress.
# claude lives only here, so it physically cannot reach anything
# outside the host except by going through cf-proxy.
# egress_net regular bridge. Only cf-proxy is dual-homed: it sits on
# proxy_net to be reachable by claude, and on egress_net so
# cloudflared can dial the Cloudflare edge.
#
# Services:
# cf-proxy cloudflared access TCP shim. Terminates the Cloudflare tunnel
# at 192.168.10.2:18443. The only door out for claude.
# claude Claude Code CLI. HTTPS_PROXY points at proxy.fhirworx.io:18443,
# which extra_hosts pins to 192.168.10.2 so the TLS SNI/cert
# match without poisoning cf-proxy's own DNS view.
#
# Usage:
# cp .env.example .env && $EDITOR .env # set PROXY_PASSWORD and
@@ -18,7 +26,7 @@
# - claude-home is a named volume so login state persists across runs.
# - Bind mounts use identical host/container paths so Claude never sees a
# translated path: /home/care/acoharmony, /opt/s3/data/workspace,
# /opt/s3/data/notebooks.
# /opt/s3/data/notebooks. Claude only has filesystem access to these.
services:
cf-proxy:
@@ -31,6 +39,7 @@ services:
networks:
proxy_net:
ipv4_address: 192.168.10.2
egress_net: {}
restart: unless-stopped
claude:
@@ -58,9 +67,12 @@ services:
networks:
proxy_net:
driver: bridge
internal: true
ipam:
config:
- subnet: 192.168.10.0/24
egress_net:
driver: bridge
volumes:
claude-home: