feat(compose): dual-mode network/socket containment — base neutral + overlays + gate (PR 1/3)#151
Merged
Merged
Conversation
Adds 29 failing tests for the dual-mode network/socket containment feature: - test/lib_compose.bats: 9 compose_files gate tests + 11 resolve_run_mode unit tests covering all precedence paths (factory default, env overrides, per-project sentinels, global default, fail-closed conflicts) - test/compose_overlay_render.bats (new): 9 render assertions via docker compose config covering contain/dood mode correctness and hardening composition; includes the hard internal:true regression guard (R8.6) All 29 tests fail (RED) — resolve_run_mode and overlay files don't exist yet.
…overlays + gate Add resolve_run_mode() and COMPOSE_DOOD/COMPOSE_CONTAIN constants to lib/compose.sh. Wire the compose_files() gate to include EXACTLY ONE of dood/contain per invocation using 4-layer precedence (env > per-project sentinel > global default > factory). docker-compose.yml becomes network-neutral: removes network_mode: host and the docker.sock bind-mount (both delivered by mode overlay instead). Adds a pointer comment directing to the overlay files and INV-9. docker-compose.dood.yml (NEW): opt-in overlay that re-adds network_mode: host + the Docker socket bind-mount. Carries INV-6 warning comment. docker-compose.contain.yml (NEW): factory-default overlay that attaches the drydock service to a drydock_net bridge (driver: bridge, NO internal: true — egress open in Phase 1 by design). No socket, no host networking. Atomicity: base-neutral + both overlays + gate land together; partial application would produce a fatal 'network_mode and networks are mutually exclusive' compose error. All 1076 tests green; shellcheck + shfmt clean.
test_projects_submount.sh: add explicit -f docker-compose.dood.yml after the base -f in run_container(). This file hardcodes compose file paths and never calls compose_files(), so DRYDOCK_DOOD=1 env alone has no effect. network_mode: host now lives in the dood overlay, not the base. session_lifecycle_compose_exec.bats: export DRYDOCK_DOOD=1 in setup() so compose_files() resolves to the dood overlay. These tests run real compose up -d + compose exec against the stack and require the Docker socket (for exec) and host networking — both removed from the base and only available in dood mode.
…r dual-mode CLAUDE.md: - INV-6 Rule: update socket description from docker-compose.yml:53 to docker-compose.dood.yml; note socket is absent in contained mode (default) - INV-6 'Where this lives in code': repoint to docker-compose.dood.yml with note that socket is opt-in and absent in the factory default (R6.10) - §3 Docker rule: replace 'DooD via the host Docker socket is the contract' with 'DooD is OPT-IN per session (dood mode); factory default is contained mode'. Consequence updated: make shell-api / Docker socket commands require dood mode (R6.11, R6.12) docs/security.md: - Socket paragraph: dood mode only carries the socket; contained mode (factory default) blocks the socket-escape class (R6.15) - Network-exfiltration paragraph: dood = host network; contained = drydock-managed bridge, no socket, no host networking; PHASE 1 NOTE: egress open, not filtered (R6.14). Pre-existing third-party tool references unchanged. No third-party project names in new prose (CC-1). No forbidden phrases (R6.7-R6.9).
Without an explicit name:, Compose namespaces the network as drydock-<proj>-<disc>_drydock_net per session. drydock's teardown is always `docker rm -f` (REQ-4 — never `compose down`), so each session leaked one network. Docker's ~31-network bridge pool would exhaust and ALL host docker networking would fail. Fix: set name: drydock_net so all contained sessions reuse ONE shared network. All sessions are isolated at the container level; network sharing is acceptable under threat model A (accidents, not adversaries). Regression guard: test/compose_overlay_render.bats — 'render: contain — network named drydock_net (fixed, not per-session namespaced)'
… mechanisms (M1) CLAUDE.md §3 Docker rule and docs/security.md referenced `drydock dood` and `drydock default dood` subcommands that do not exist in PR1 — only DRYDOCK_DOOD=1 env override and ~/.config/drydock/dood/<proj> sentinel files are implemented. Shipped docs pointing at unknown commands would mislead users immediately. Replace the CLI references with the mechanisms that exist in PR1: - DRYDOCK_DOOD=1 env override (per-invocation) - ~/.config/drydock/dood/<proj> sentinel (per-project pin) - ~/.config/drydock/default-dood sentinel (global default) Add a brief parenthetical noting the ergonomic drydock dood / drydock default CLI lands in a later slice (PR2). All See INV-9 pointers kept as-is (forward INV reference is fine; a nonexistent command is not).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Splits drydock's network/socket posture into two distinct compose modes:
docker-compose.yml— now network-neutral: no host networking, no Docker socket, no hardcoded network config. This is the starting point; it carries no mode opinion.docker-compose.dood.yml— DooD overlay: host networking + Docker socket bind-mount. Restores the current behaviour for users who need Docker-out-of-Docker. Opt-in.docker-compose.contain.yml— contained overlay: fixed-namedrydock_netbridge network (nointernal: true; egress to the internet stays open), no Docker socket. This becomes the factory default — the mode that runs when no explicit preference is set.A
resolve_run_mode()helper + updatedcompose_files()gate select exactly one overlay via a 4-layer precedence stack (highest → lowest):DRYDOCK_DOOD=1/DRYDOCK_CONTAIN=1~/.config/drydock/{dood,contain}/<project>~/.config/drydock/default-doodThe logic is fail-closed: conflicting signals (both env vars set, both per-project sentinels present) abort with a clear error rather than silently choosing one.
Doctrine deltas shipped in this PR (they become active falsehoods the moment the base goes neutral):
docker-compose.dood.yml(the socket now lives in the DooD overlay, not the base)Chain context
PR 1 of 3 — stacked to
dev:feat/dual-mode-compose-corefeat/dual-mode-commandsdrydock default/dood/containCLI subcommands + startup bannerfeat/dual-mode-docsPart of #149.
Invariants touched
docker-compose.dood.yml(the DooD overlay) instead of the basedocker-compose.yml. The socket still exists; it has moved to an explicit opt-in overlay, which strengthens rather than weakens the boundary.cap_drop,no-new-privileges, tmpfs) live indocker-compose.hardening.ymland are independent of the DooD/contain split.Test plan
scripts/test.sh); shellcheck, shfmt, and lint-commits clean.drydock_netbridge (nointernal: true) reachedapi.anthropic.com→ HTTP 405 (method not allowed, but TLS handshake + DNS completed). Egress works in contained mode./etc/hostnameenvironment artifact unrelated to this change; integration tests are gated out of default CI.Known follow-ups (not in this PR)
drydock doctoractive-mode reporting → PR 2