Skip to content

chore(egress): release-gate harness — G1 capture + G2 double-gate smoke#157

Merged
jraicr merged 1 commit into
devfrom
feat/egress-gate-harness
Jun 5, 2026
Merged

chore(egress): release-gate harness — G1 capture + G2 double-gate smoke#157
jraicr merged 1 commit into
devfrom
feat/egress-gate-harness

Conversation

@jraicr

@jraicr jraicr commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

What

Tooling for the v0.4.0 egress-jail release gates G1 (empirical baseline CONNECT
capture) and G2 (runtime double-gate smoke). Maintainer-run, bare Linux, NOT in
CI
. The egress jail itself already shipped (#154/#155/#156). No production file
is touched
— three new files only.

Files

  • scripts/egress-capture.sh (G1) — enable / collect / finalize /
    disable subcommands. enable backs up ~/.config/drydock/egress-allowlist
    and appends a temporary .* allow-all so the per-session sidecar (which already
    runs LogLevel Connect) logs every CONNECT; collect harvests unique requested
    hosts from docker logs; finalize prints anchored-ERE baseline candidates not
    already in the shipped baseline; disable restores the backup. Idempotent;
    double-enable guarded so the real backup can't be clobbered.
  • scripts/egress-smoke.sh (G2) — brings up a standalone sidecar on the real
    drydock_internal/drydock_egress networks, mirroring prod hardening
    (cap_drop ALL, no-new-privileges, read_only + tmpfs /run+/tmp), and
    probes through the proxy: allowlisted host tunnels (CONNECT 200),
    non-allowlisted + IP-literal are refused (CONNECT 403). Block-detection reads
    curl %{http_connect} (the proxy CONNECT code) — not %{http_code} (the
    tunneled GET, which is 000 on refusal). Cleanup trap removes the smoke sidecar;
    the shared fixed-name networks are left in place.
  • test/egress_gate.bats — 17 unit tests for the pure capture helpers
    (_extract_connect_hosts, _to_ere_baseline_pattern): both tinyproxy line
    forms, dedup, IP-literals, noise rejection, a malformed-line no-raw-leak guard,
    and ERE anchoring/dot-escaping.

Verification

  • scripts/test.sh1154/1154 green (1137 prior + 17 new).
  • shellcheck + shfmt (CI args) clean.
  • Strict TDD on the pure helpers (tests written first).
  • Fresh adversarial review (opus) — found + fixed a CRITICAL (the smoke probe
    was keying off %{http_code} instead of %{http_connect}, so the block gates
    could never pass) plus four WARNINGs (temp-file leak on missing baseline, smoke
    sidecar hardening fidelity, a parser raw-line leak on a malformed log line, and a
    grep -qF '.*' substring false-positive in enable). All applied.

Honest caveat

The tinyproxy LogLevel Connect line format is assumed (debian:12 tinyproxy);
it cannot be verified in WSL2. Both scripts dump raw docker logs and print a
"confirm log format on first real run" notice, and the bats tests validate parser
internal consistency only — not real tinyproxy output. The first real collect
run is the format gate.

Size

size:exception — 757 insertions, but all-new additive tooling that touches no
production code, is cohesive (G1+G2+tests as one harness), and has already passed
an adversarial review. Splitting would be artificial (G1 alone is ~488 lines).

Not in this PR

Running the gates (G1/G2) and the v0.4.0 release (G3) remain maintainer steps on a
bare-Linux host. Does not close #149. Refs #149.

Tooling for the v0.4.0 egress-jail release gates (maintainer-run, bare Linux,
NOT in CI). No production files touched.

- scripts/egress-capture.sh (G1): enable/collect/finalize/disable subcommands.
  enable appends a temporary `.*` allow-all to the global allowlist (backed up
  first) so the per-session sidecar — which already runs LogLevel Connect — logs
  every CONNECT; collect harvests unique requested hosts from `docker logs`;
  finalize prints anchored-ERE baseline candidates not already shipped; disable
  restores the backup. Pure parser helpers are unit-tested.
- scripts/egress-smoke.sh (G2): brings up a standalone sidecar on the real
  drydock_internal/drydock_egress networks (mirroring prod hardening: cap_drop
  ALL, no-new-privileges, read_only + tmpfs) and probes via the proxy — an
  allowlisted host tunnels (CONNECT 200); non-allowlisted and IP-literal are
  refused (CONNECT 403). Block-detection reads curl %{http_connect} (the proxy
  CONNECT code), not %{http_code} (the tunneled GET, which is 000 on refusal).
- test/egress_gate.bats: 17 unit tests for the pure capture helpers
  (_extract_connect_hosts, _to_ere_baseline_pattern), incl. malformed-line and
  dedup edge cases.

The tinyproxy log format is assumed (debian:12 tinyproxy); both scripts dump raw
logs and print a "confirm format on first real run" notice. 1154/1154 bats green;
shellcheck + shfmt clean.
@jraicr jraicr added type:chore Chore / housekeeping size:exception Over 400 lines, pre-approved by maintainer labels Jun 5, 2026
@jraicr jraicr merged commit 15ae6d7 into dev Jun 5, 2026
3 checks passed
@jraicr jraicr deleted the feat/egress-gate-harness branch June 5, 2026 20:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:exception Over 400 lines, pre-approved by maintainer type:chore Chore / housekeeping

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant