Skip to content

feat(msb): mirror the sbx balanced network policy as the msb default egress - #295

Merged
mogul merged 16 commits into
mainfrom
feat/msb-balanced-egress
Aug 11, 2026
Merged

feat(msb): mirror the sbx balanced network policy as the msb default egress#295
mogul merged 16 commits into
mainfrom
feat/msb-balanced-egress

Conversation

@mogul

@mogul mogul commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Context

sbx ships a curated balanced network policy (AI services, package registries, code/container hosts, cloud infra, OS package mirrors, and cert validation) and the sbx path in this repo assumes it. msb has no equivalent default — its low-level model is deny-by-default with an ordered rule list, and its high-level profiles (public/private/host) do not match the sbx balanced host set.

Left alone, an acq-created msb sandbox reached only the hosts the four built-in kits declare (api.gsa.usai.gov, api.github.com) plus npm when an agent is installed — dramatically more locked down than the equivalent sbx sandbox. A user who ran pip install, cargo build, apt-get, etc. inside an msb sandbox would fail on egress that "just works" on sbx.

This branch gives every msb sandbox the same egress as sbx balanced by default, composed on top of the kits' own caps.network.allow. See ADR-0018 for the full decision record.

I've also filed an upstream feature-request on msb that would make this way less messy.

What changed

  • Vendored host listacq.backends/msb-balanced-hosts.txt, a human-auditable, #-commentable verbatim mirror of the host:port rows from sbx policy inspect local-policy. Kept un-translated so it stays trivially diffable against fresh sbx output; it is the single source of truth an operator edits when sbx balanced changes.
  • Translation in one place_acq_msb_balanced_rules_into (+ helpers _acq_msb_balanced_target / _acq_msb_balanced_port_ok / _acq_msb_balanced_parse_line) in acq.backends/msb.sh translate each entry to an msb --net-rule per the grammar allow[:egress]@<target>[:<proto>[:<ports>]]:
    • **.host / *.host (sbx multi-label glob) → msb domain-suffix *.host (msb suffixes match the apex and any subdomain at any depth, so this loses no coverage).
    • :PORT:tcp:<port>; a host on both :80 and :443 yields two rules, preserved per-port.
    • crl*.digicert.com (an intra-label glob msb cannot express) → broadened to *.digicert.com, with a one-time widening note (preserves CRL reachability on :80).
    • Gateway DNS is granted explicitly so the guest can resolve allowed hosts under --net-default deny. See the DNS note below.
  • Restrict, don't merely widen — at create the adapter emits --net-default deny plus the generated allow@… rules, so egress is restricted to the balanced set (deny-by-default + allowlist) — genuine parity with sbx balanced, not "allow-all with extras".
  • Toggle, on by defaultACQ_MSB_BALANCED_EGRESS (default on) enables the baseline; 0/false/no/off/empty disables it and falls back to kit-only egress (no deny-default emitted). ACQ_MSB_BALANCED_HOSTS_FILE overrides the list path for a site-specific set.
  • Docs — ADR-0018, docs/BACKEND_GUIDE.md, and docs/KNOWN_FAILURE_MODES.md (drift/re-sync procedure + the DNS-macro failure mode below).

DNS: worked around an upstream msb bug

The baseline needs a gateway-DNS grant under --net-default deny. It originally emitted msb's semantic allow@dns macro, which hard-fails msb create/msb run on released msb (reproduced on 0.6.8):

error: the `dns` target supports `tcp`, `udp`, or `any`, not `dns`

Root cause is an upstream msb parser bug, not an acq misconfiguration: the macro's parser advances past the dns target token only inside a debug_assert_eq!, which is compiled out of a release binary — so the same dns token is re-read as the protocol slot and rejected. A bare allow@dns therefore fails on every release build that has the macro (0.6.7+).

The adapter now emits the expanded equivalent — exactly what the macro is specified to produce, on the ordinary (assert-free) parse path:

--net-rule allow@host:udp:53 --net-rule allow@host:tcp:53

Documented as failure mode 32 in docs/KNOWN_FAILURE_MODES.md, with a quarterly re-verify note to collapse it back once upstream fixes the macro.

Files

File Change
acq.backends/msb-balanced-hosts.txt New vendored balanced host list (verbatim sbx mirror)
acq.backends/msb.sh Balanced-egress emitter + translation helpers + DNS workaround
scripts/test-acq Offline coverage for translation, ports, DNS rules, toggle, real-file parse
docs/adr/0018-msb-balanced-egress-baseline.md ADR
docs/BACKEND_GUIDE.md ACQ_MSB_BALANCED_EGRESS / host-file env vars + egress model
docs/KNOWN_FAILURE_MODES.md Drift/re-sync (31) + allow@dns macro bug (32)

Verification

  • Offline unit harness: ./scripts/test-acq759 passed, 0 failed. Covers target translation (**., crl*, exact, single-label rejection), port validation, dual-port hosts, the expanded gateway-DNS rules (and that the broken allow@dns macro is never emitted), malformed-line skipping, a full parse of the real vendored file with no skips, and the default-on / =0-off provision paths.
  • Lint: shellcheck --severity=warning (one file per invocation, per the AGENTS.md hang note) — clean.
  • Live repro of the fixed bug (host, msb 0.6.8): msb run --net-default deny --net-rule "allow@dns" alpine -- true reproduces the upstream failure; the adapter's expanded rules avoid it.

Live end-to-end validation — done (host, per ADR-0018 / ADR-0011)

The full acq runmsb create → egress-parity check needs a KVM-capable host and cannot run inside a sandbox (no nested sandboxes). Ran from the host on the review/feat/msb-balanced-egress checkout; all four observations confirmed:

  • Sandbox created successfully — the acq run / msb create that previously aborted with error: the dns target supports tcp, udp, or any, not dns now completes.
  • DNS resolves in the guest — name resolution works inside the sandbox, so the expanded allow@host:udp:53 + allow@host:tcp:53 rules do their job under --net-default deny.
  • Allowed host reachable — a balanced-only host that no kit allows (e.g. pypi.org:443) is reachable from inside the sandbox.
  • Unlisted host refused — a host outside the balanced set is blocked, so the deny-by-default allowlist holds (parity with sbx balanced, not allow-all).

Rollback

Revert the branch (or, for just the DNS fix, revert e1ecc1e). At runtime, set ACQ_MSB_BALANCED_EGRESS=0 to disable the baseline entirely and fall back to kit-only egress (no behavior change vs. pre-branch msb).

Security impact

  • SC-7 (boundary protection): intentionally widens the default msb egress from kit-hosts-only to the full sbx balanced set — but remains an allowlist under deny-by-default, not allow-all. The crl**.digicert.com broadening is the only place msb is intentionally wider than sbx, and it is logged.
  • SI-10 (input validation): the semi-trusted host list drives create-time flags, so every translated target is charset-validated ([A-Za-z0-9.*_-]), single-label suffixes (*.com) are rejected, and every port is integer-validated (1–65535) before it reaches argv.
  • No new external services; the secret-injection and TLS-interception model (SC-8) is unchanged. Configuration/adapter change only (CM-2/CM-6/CM-7; SA-8/SA-15).

AI assistance

AI-assisted (OpenCode, model claude_4_8_opus); see the Co-authored-by: trailer on e1ecc1e. Human-owned and reviewed by@mogul.

OpenCode and others added 6 commits August 7, 2026 20:13
Mirror the sbx "balanced" (local-policy) network allow-list as a
human-auditable data file. The msb backend will translate each entry
into an msb --net-rule so msb sandboxes get the same egress as sbx
"balanced" (msb defaults egress to none).

Refs: ADR-0018
Add ACQ_MSB_BALANCED_EGRESS (default on) + ACQ_MSB_BALANCED_HOSTS_FILE.
_acq_msb_balanced_rules_into translates the vendored host list into msb
--net-rule tokens (**.->*. suffix, crl*.->parent suffix with a warning,
:port->:tcp:port, plus allow@dns) and acq_backend_provision emits them
with --net-default deny so egress is restricted to the balanced set,
matching sbx 'balanced'. Helpers kept under the 50-line limit.

Refs: ADR-0018
Add offline unit + provision tests for the balanced egress baseline
(target translation, port validation, dual-port hosts, crl* broadening,
malformed-line skip, real-file parse, and the default-on / =0-off
provision paths).

Fix: the documented ACQ_MSB_BALANCED_EGRESS=0 off-switch did not work —
the guard tested `[ -n "0" ]` (non-empty, so on). Normalize the value
to "1"/"" up front, treating 0/false/no/off/empty (case-insensitive)
as off.

Refs: ADR-0018
Add ADR-0018 (mirror the sbx "balanced" policy as msb's default
egress), the ACQ_MSB_BALANCED_EGRESS / ACQ_MSB_BALANCED_HOSTS_FILE
tunables and a network-egress section in BACKEND_GUIDE.md, and a
KNOWN_FAILURE_MODES entry for host-list drift + the re-sync procedure.
- Accept sbx-'balanced' parity: registry.npmjs.org is in the balanced
  set, so every sandbox (incl. shell) reaches it when the baseline is on.
  Rescope test 8n4 to the kit-only mode (ACQ_MSB_BALANCED_EGRESS=0),
  which is where the agent-conditional npm gate still applies, and
  document the interaction in ADR-0018.
- Reject leading-zero ports (e.g. 0443) in _acq_msb_balanced_port_ok.
- Extract _acq_msb_balanced_parse_line so _acq_msb_balanced_rules_into
  drops from 51 to 42 lines (under the 50-line limit).

Refs: ADR-0018
msb's semantic `allow@dns` macro hard-fails `msb create`/`msb run` on
released builds (reproduced on 0.6.8) with:
  error: the `dns` target supports `tcp`, `udp`, or `any`, not `dns`

The macro's parser advances past the `dns` target token only inside a
`debug_assert_eq!`, which is compiled out of the release binary, so the
same `dns` token is re-read as the protocol slot and rejected. This broke
the balanced-egress baseline (ADR-0018), which emitted `allow@dns` under
`--net-default deny`.

Emit the expanded equivalent the macro is specified to produce — the
gateway `host` group on UDP/TCP port 53 — via the ordinary, assert-free
parse path, which is unaffected by the upstream bug:
  --net-rule allow@host:udp:53 --net-rule allow@host:tcp:53

Update the balanced-egress tests to assert the expanded rules and that the
broken `allow@dns` macro is never emitted; document the upstream bug and
workaround in KNOWN_FAILURE_MODES (#32), ADR-0018, and BACKEND_GUIDE.

Verified: scripts/test-acq (759 passed, 0 failed); shellcheck clean.

Co-authored-by: OpenCode [claude_4_8_opus] <bret.mogilefsky@gsa.gov>
@mogul

mogul commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Note this is all a seed for a larger epic.

The balanced-egress baseline emitted the symmetric `--net-default deny`,
which msb applies to BOTH directions. Published ports (`-p HOST:GUEST`)
get no implicit ingress-allow, so every inbound connection fell through
to the ingress default and was RST-rejected: a host `curl` to a
create-time published port completed the TCP handshake via msb's host
proxy, then reset on data (ERR_CONNECTION_RESET), even though the guest
service was healthy and listening.

Emit `--net-default-egress deny` instead. Egress stays deny-by-default +
allowlist (ADR-0018's intent) while ingress keeps msb's baseline `allow`,
so create-time published ports are reachable with no per-port rule. A
future "strict" profile can layer ingress deny-default + explicit
`allow:ingress@...` rules; the balanced default intentionally does not.

Requires the `--net-default-egress`/`--net-default-ingress` split
(confirmed on the pinned msb 0.6.8).

- msb.sh: emit `--net-default-egress deny`; update baseline + DNS comments
- test-acq: assert egress-only deny-default, and NOT a symmetric
  `--net-default deny` nor an ingress deny
- ADR-0019: document the egress-only decision + strict-profile hook;
  amend ADR-0018; update BACKEND_GUIDE
- scripts/verify-net-default-egress: live regression guard (create-time
  publish reachable + egress still locked)
@mogul

mogul commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Fix: create-time published ports were unreachable under the balanced-egress default (ERR_CONNECTION_RESET)

Follow-up on this branch: create-time published ports (-p HOST:GUEST, e.g. the
openchamber UI on 3000 and the OpenCode server on 4096) were unreachable from the
host. A host curl completed the TCP handshake and then reset
(Recv failure: Connection reset by peer / ERR_CONNECTION_RESET), even though
the guest service was healthy and listening on 0.0.0.0.

Root cause: the baseline emitted the symmetric --net-default deny. In
msb that flag sets the default for both directions (--help: "Sets egress
and ingress symmetrically"
). A published port has no implicit ingress-allow —
msb evaluates every accepted connection against default_ingress — so a
symmetric deny RST-rejects inbound. (Post-hoc acq ports --publish was
unaffected because its ssh -L forward originates inside the guest, so it isn't
evaluated as ingress — which is why that workaround kept working.)

Fix: emit --net-default-egress deny instead. Egress stays deny-by-default +
balanced allowlist (this PR's intent); ingress keeps msb's baseline allow, so
published ports are reachable with no per-port rule. Ingress deny-default is
intentionally reserved for a future opt-in "strict" profile. Requires the
--net-default-egress/--net-default-ingress split (confirmed on the pinned
msb 0.6.8).

Changes

  • acq.backends/msb.sh: emit --net-default-egress deny; comment updates
  • scripts/test-acq: assert egress-only deny-default (and NOT a symmetric
    --net-default deny nor an ingress deny) — 761/761 pass
  • docs/adr/0019-msb-balanced-egress-is-egress-only.md (new); amend ADR-0018;
    update docs/BACKEND_GUIDE.md
  • scripts/verify-net-default-egress (new): live regression guard for the
    create-time NAT publish path

Verification

  • Offline: scripts/test-acq — 761 passed, 0 failed
  • Live (KVM host): scripts/verify-net-default-egress — passed (host reaches a
    create-time published port with no acq ports --publish; in-guest egress to an
    unlisted host is still refused)

Security posture: unchanged for egress (deny-by-default + balanced
allowlist). Ingress default-allow matches msb's own profile baseline
(from_profiles: default_egress: Deny, default_ingress: Allow) and does not
widen egress; inbound is still bounded by which ports are published.

@wz-gsa

wz-gsa commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Adversarial review — #295 (msb balanced egress)

I read the emitter, the vendored host list, both ADRs, and the test deltas, then verified every upstream-msb claim against the microsandbox source on main (crates/cli/lib/net_rule.rs, crates/network/lib/policy/types.rs, crates/cli/lib/commands/common.rs). Verdict: the design is sound and the two hard technical claims check out. One blocking correctness gap, then should-fix / nits.

What I verified as CORRECT (receipts)

  • allow@dns release-build bug (ADR-0018 DNS block). Confirmed. parse_dns_rule advances the target token inside debug_assert_eq!(parts.next(), Some("dns")); in a release build the macro (and its parts.next() side effect) is compiled out, so proto_raw = parts.next() reads "dns" and returns InvalidDnsProtocol{raw:"dns"} → the exact error string quoted. The expanded workaround allow@host:udp:53 + allow@host:tcp:53 is provably equivalent to what parse_dns_rule is specified to emit (Destination::Group(Host), protocols [Udp,Tcp], port 53). ✅
  • ADR-0019 egress-only reasoning. Confirmed against build_network_policy: with rules + --net-default-egress deny and no --net profile, default_ingress falls through to NetworkPolicy::default() (from_profiles([Public])) = Action::Allow. So published ports stay reachable; a symmetric --net-default deny would have set default_ingress = Deny and RST inbound. The ERR_CONNECTION_RESET root-cause is accurate. ✅
  • Wildcard translation + single-label rejection. *.host in msb matches apex + any depth (matches_suffix is label-aligned, so evilexample.com does NOT match example.com), and *.com is rejected upstream as SuffixTooBroad — the adapter's own single-label drop is correct defense-in-depth. **.*. loses no coverage. ✅
  • :80 / no-SNI hosts (CRL/OCSP) still match via the resolved-hostname cache (HostnameSource::CacheOnlyany_resolved_hostname), given the DNS grant is emitted first. ✅
  • SI-10 hardening (charset guard, leading-zero port reject, malformed-line skip, CRLF \r rejected by the charset class) — all fail-safe. ✅
  • No secrets/PII/internal hosts in the vendored list (all public vendor endpoints). ✅

BLOCKING — version-floor gap makes the default break acq on msb 0.6.0–0.6.7

--net-default-egress is a 0.6.8 flag (the PR says so), but MIN_MSB_VERSION stays 0.6.0 and the balanced block is default-ON with no per-emit version guard (acq.backends/msb.sh, the if [ -n "$ACQ_MSB_BALANCED_EGRESS" ] block ~L1774). On any msb in [0.6.0, 0.6.8) a plain acq create/acq run will pass --net-default-egress deny to a binary that doesn't know the flag → clap hard-errors → every sandbox creation fails out of the box, and the user has no obvious signal that ACQ_MSB_BALANCED_EGRESS=0 is the escape hatch. Since balanced-on is the default, this regresses the common path for anyone not yet on 0.6.8.

Pick one:

  1. Bump MIN_MSB_VERSION to 0.6.8 (cleanest — acq_backend_prepare already fails closed on the floor), or
  2. Guard the emit: only add --net-default-egress deny + the balanced block when _acq_msb_version_ge "$current" 0.6.8; below that, warn once and fall back to kit-only egress.

I'd take (1) unless you must keep 0.6.7 working. Either way add a test asserting the sub-0.6.8 path doesn't emit the flag.

Should-fix

  • registry.npmjs.org double-emit. With balanced ON, the host list already allows registry.npmjs.org:tcp:443, then the agent-install block emits a second bare allow@registry.npmjs.org (any port). Harmless (both allow, no deny to shadow), but dead-weight. Consider skipping the npm rule when balanced is ON, or note the intentional redundancy.
  • QUIC/HTTP-3 to allowed :443 hosts is silently denied. --tls-intercept blocks QUIC unless --no-block-quic, and the balanced rules are tcp:443 only (no udp:443). Net: HTTP/3-only egress fails and clients fall back to TCP — same as sbx balanced, so acceptable, but worth one line in BACKEND_GUIDE so a slow-first-connection isn't mistaken for a bug.

Nits

  • crl*.digicert.com*.digicert.com also matches www./order. digicert and makes the sibling *.one.digicert.com / cacerts. / ocsp.digicert.com entries redundant. Bounded to one vendor and logged — fine, just noting the redundancy.
  • Ingress default-allow blast radius (documented, but restate for the record): inbound reachability is bounded by which ports are actually published (-p) + the guest bind, and msb has no NAT ingress for unpublished ports, so ingress-allow is not a general exposure. The future strict-profile hook (per-port allow:ingress@…) is the right place to tighten. No action.

Neutral-tier convergence (my ADR #299 / epic #303)

This is the msb worked example my neutral network.tier ADR generalizes; I've already cross-referenced #295/ADR-0018/0019 there and set the neutral default to balanced to match you. When #300/#294 land, this emitter + vendored list should be adopted under the tier (baseline location TBD per your ADR-0018 "migrate the vendored file into a patterns kit" note), and the ACQ_MSB_BALANCED_EGRESS toggle reconciled with the strict/balanced/open selector.

Net: strong PR, verified deep. Fix the version-floor blocker and I'd approve.

AI-assisted (OpenCode); adversarial review, upstream-source-verified. Not an approval — flagging one blocker.

Address the adversarial review on the balanced-egress PR:

- Bump MIN_MSB_VERSION 0.6.0 -> 0.6.8. The balanced-egress default emits
  --net-default-egress deny, a flag that first appears in msb 0.6.8; on an
  older binary a plain `acq create` passed an unknown flag to `msb create`
  and clap hard-errored mid-create. acq_backend_prepare now fails closed with
  a clear version message on any sub-0.6.8 msb.
- De-dupe the npm-install allow rule: registry.npmjs.org is already in the
  balanced set, so with the baseline ON the adapter no longer emits a second
  bare allow@registry.npmjs.org. An override host (ACQ_MSB_NPM_HOSTS) NOT in
  the balanced set still gets its rule.
- Document QUIC/HTTP-3 fallback in BACKEND_GUIDE (balanced is tcp:443 only;
  --tls-intercept blocks QUIC; clients fall back to TCP, same as sbx).
- Update BACKEND_GUIDE requirements table, ADR-0018, and ADR-0019 for the
  0.6.8 floor.
- Tests: assert sub-0.6.8 rejection + 0.6.8 acceptance; npm de-dupe (default
  registry elided, override host emitted); stub msb version -> 0.6.8.
  scripts/test-acq: 766 passed, 0 failed.

Co-authored-by: OpenCode [claude-opus-4] <bret.mogilefsky@gsa.gov>
@mogul

mogul commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Review response — version-floor blocker + should-fixes addressed (fe0c8f7)

Thanks for the deep, upstream-verified review, @wz-gsa. All actionable items are addressed. Summary below; everything landed in fe0c8f7.

BLOCKING — version-floor gap (fixed, option 1)

Bumped MIN_MSB_VERSION 0.6.0 → 0.6.8. Since the balanced-egress baseline is on by default and emits --net-default-egress deny (a 0.6.8 flag), acq_backend_prepare now fails closed with a clear version message on any 0.6.0–0.6.7 binary, instead of letting msb create hit an unknown flag and clap-error mid-create. Added a test asserting the sub-0.6.8 path is rejected (rc 1, message names 0.6.8) and 0.6.8 is accepted.

Should-fix — registry.npmjs.org double-emit (fixed)

The balanced block now records the bare host of each rule it emits; the npm-install block skips any ACQ_MSB_NPM_HOSTS entry the balanced set already covers. Kept it host-level rather than skipping the whole block, so an internal-mirror override not in the balanced set still gets its rule. Tests cover both: default registry elided, override host still emitted.

Should-fix — QUIC/HTTP-3 (documented)

Added a BACKEND_GUIDE.md note: balanced rules are tcp:443 only and --tls-intercept blocks QUIC unless --no-block-quic, so HTTP/3-only egress falls back to TLS-over-TCP (same as sbx balanced); a one-time slow first connection is expected, not a bug.

Nits

crl*.digicert.com*.digicert.com broadening and the ingress default-allow blast radius were both flagged as already-logged / no-action — left as-is per your review.

Docs

Updated the BACKEND_GUIDE.md requirements table (>= 0.6.8), ADR-0018 (npm de-dupe note), and ADR-0019 (version-floor rationale + sub-0.6.8 fail-closed).

Verification

  • Offline: scripts/test-acq766 passed, 0 failed (+5 assertions across the new blocks).
  • bash -n clean on acq.backends/msb.sh and scripts/test-acq; npm run lint:md — 0 errors.
  • Live (KVM host, deferred per ADR-0011): unchanged from the prior verify-net-default-egress run; the change is a version-floor gate + rule de-dupe, not a change to the emitted egress semantics.

Neutral-tier convergence

Your ADR #299 / epic #303 convergence note is tracked as future cross-repo work, not part of this PR.

AI-assisted (OpenCode).

@mogul
mogul requested a review from wz-gsa August 10, 2026 17:38
mogul added 8 commits August 10, 2026 18:02
Add warn_if_published_ports_dead — a backend-neutral attach-time check
(uses only acq_backend_ports + acq_backend_run) that surfaces the
"ports mapped, kit services dead" state instead of a later opaque
"Unable to connect". Wire it into both the create-and-attach and
re-attach-by-name paths. Document as KNOWN_FAILURE_MODES.md #33.

The companion msb heal fix (fold ACQ_CLI_KITS into
acq_backend_ensure_kits_applied) and its clikit-heal + ports-dead tests
landed in a prior commit on this branch.

Co-authored-by: OpenCode [claude-opus-4] <bret.mogilefsky@gsa.gov>
The out2 variable captured msb 0.6.8 prepare output but only rc2 was
checked, tripping shellcheck SC2034. Add an assertion that the 0.6.8
binary emits no version-floor error, which uses out2 and strengthens
the test.

@wz-gsa wz-gsa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review — all prior findings resolved ✅

Re-reviewed against my earlier adversarial pass. Every blocker/should-fix is fixed, verified in the diff:

  • BLOCKER (version floor) — FIXED. MIN_MSB_VERSION raised 0.6.0 → 0.6.8 with a comment explaining 0.6.8 is the first release with the --net-default-egress/--net-default-ingress split. acq_backend_prepare fails closed before the emit path, so the flag is always known — the sub-0.6.8 "unknown flag → every create fails" regression is gone.
  • allow@dns release-build bug — FIXED the way my review confirmed: expands to allow@host:udp:53 + allow@host:tcp:53 (equivalent to parse_dns_rule's spec output) instead of the macro that breaks in release builds. Documented in ADR-0018.
  • Egress-only correctness — CORRECT (ADR-0019). Emits --net-default-egress deny, NOT symmetric --net-default deny, so ingress stays at msb's baseline allow and create-time published ports (-p) stay reachable (no ERR_CONNECTION_RESET). Comment cites the msb 0.6.8 --help "sets egress and ingress symmetrically" behavior. The future strict-profile ingress-deny hook is noted as deliberately out of scope. ✅
  • npm double-emit — FIXED. The npm block now de-dupes against the balanced set (skips registry.npmjs.org when the baseline already allow-listed it). ✅
  • crl*.digicert.com widening — handled + logged (broadened to *.digicert.com with a one-time warning; msb has no intra-label glob). ✅

New in this revision, reviewed:

  • GitHub token host scope expanded api.github.comgithub.com,api.github.com,codeload.github.com. This is a deliberate, justified change: the playbook kit moved from a REST-tarball fetch to real git transport (clone/push), which needs github.com + codeload. Still per-sandbox scoped, still bound via --secret (real value never enters the VM), still 3 explicit hosts (not a wildcard) — least-privilege intact. ADR-0013 still applies. No objection.
  • verify-net-default-egress script + test-acq coverage — good: the egress-only behavior is now regression-tested (deny-default emitted, DNS rules present, ACQ_MSB_BALANCED_EGRESS=0 toggle path).
  • "published port but nothing listening" attach-time warning — advisory (warns, does not block), turns a confusing "connection refused" into a clear note. Good DX, correctly non-blocking. ✅

Verification: all CI green (Conventional Commit, Link Check, Markdown Lint, pre-commit). Converges cleanly with the neutral network.tier ADR (patterns #299) — this is the msb worked example that generalizes to.

APPROVE. Strong work; the egress-only + version-floor handling is exactly right.

AI-assisted (OpenCode); adversarial re-review, all prior findings verified fixed.

@mogul
mogul merged commit 2c056cb into main Aug 11, 2026
4 checks passed
@mogul
mogul deleted the feat/msb-balanced-egress branch August 11, 2026 04:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants