name: member-console-audit networks: # Proxy reaches the internet through here. egress: driver: bridge labels: { com.member-console.audit: "1" } # Auditor lives here ONLY. `internal: true` means no route off-box except # via a container that also sits on `egress` — i.e. the proxy. Fail-closed. isolated: driver: bridge internal: true labels: { com.member-console.audit: "1" } services: egress-proxy: build: context: . dockerfile: Dockerfile.proxy image: member-console-audit-proxy:latest networks: [egress, isolated] volumes: - ./tinyproxy.conf:/etc/tinyproxy/tinyproxy.conf:ro - ./allowlist.txt:/etc/tinyproxy/allowlist.txt:ro labels: { com.member-console.audit: "1" } auditor: build: context: . dockerfile: Dockerfile image: member-console-audit:latest # Auditor is on the isolated (internal) network only. Its sole path out is # the proxy, addressed by name; the proxy enforces the host allowlist. networks: [isolated] environment: HTTPS_PROXY: http://egress-proxy:8888 HTTP_PROXY: http://egress-proxy:8888 ALL_PROXY: http://egress-proxy:8888 NO_PROXY: localhost,127.0.0.1,egress-proxy # Scanners must not reach for modules at run time; the cache is baked in. GOPROXY: "off" GOFLAGS: "-mod=mod" volumes: # Disposable git-archive copy — the auditor's writable working tree. # The real repository is never mounted. - ./.code:/src # The only path that survives the run: the caller's findings directory. # Every script that produces findings exports OUT; the fallback exists # so `docker compose build` parses this file without it. - ${OUT:-./.run/out}:/out # No credential is mounted here. A lane adds the one file its harness # signs in with at `run` time (harness.sh); the scanner pass gets none. working_dir: /src labels: { com.member-console.audit: "1" }