Repository navigation
T102 — Flows to host.min.internal are refused outside the design's openings - #2871
norrietaylor merged 17 commits into
Conversation
The egress gate now refuses a flow to the host alias unless a port in the current [host-exposures] table of the client config.toml opens it. The row's own rules then decide the flow. The node-plane identity alone also reaches the ports that the baseline set's own endpoints open at the alias. A box row and the host-address cohort never get that set. minvmd reads the table again when the file changes, so an added entry opens its port and a removed entry closes it without a restart. This retires MINVMD_HOST_EXPOSURE_PORTS. Each refused flow logs under the host-alias rule with the row and the port.
|
Important Review skippedBot user detected. To trigger a single review, invoke the
|
|
Approved: every review thread resolved at @norrietaylor resolved the last thread, which releases the approval this review had withheld. Nothing to do. |
There was a problem hiding this comment.
arch-review: CONDITIONS · 4 conditions · the full ruling
The gate is in the right place, and the alias is default-deny for every row. The table format does not follow §7.1 line 250. The reader skips the documented { proto, port } form, and the gate opens UDP at every exposure. C1 fixes this. A box can reach the baseline set's alias port through an exposure, and line 252 forbids that. C2 fixes this. The session e2e and docs/reference/cli-minvmd.md still use the retired MINVMD_HOST_EXPOSURE_PORTS. C3 and C4 fix them.
Ruled at 3a43528 against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.
The variable no longer opens a port at host.min.internal. minvmd now logs a warning that names it and points to the [host-exposures] table of the client config.toml, so an operator who still sets it sees why the alias stays closed at those ports.
There was a problem hiding this comment.
arch-review: CONDITIONS · 4 conditions · the full ruling
The gate is in the right place and fails closed. Three §7.1 rules are not met yet. The reader skips the entry form that line 250 gives, name = { proto = "tcp", port = N }, and accepts ranges (C1). An exposure opens UDP, but proto defaults to tcp (C2). A table port that a Minimal listener or a baseline endpoint holds is open to every box, and line 252 refuses this (C3). The new warning moved the Linux cfg gate off signal_via_pidfd, and the macOS build breaks (C4).
Ruled at a867cb8 against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.
|
Round 1 of 10 on the runtime's review findings, pushed as
Verify Review threads the coder judged:
On a VM host, a flow from any box to host.min.internal reaches only the ports that the current [host-exposures] table of the client config.toml opens, the gateway resolver and the credentialed lane's steered Box Egress Proxy flow. The node alone also reaches the node-plane baseline set there. If MINVMD_HOST_EXPOSURE_PORTS is still set, the minvmd log now says that minvmd ignores it. Notes from the agent: Revise round 1: minvmd now logs a warning when MINVMD_HOST_EXPOSURE_PORTS is set (crates/minvmd/src/net.rs). The doc and the e2e script are outside this task's layers, so this round did not change them. A follow-up must change docs/reference/cli-minvmd.md line 58 to describe the [host-exposures] table. It must also change scripts/session-e2e.sh (lines 395-406, 21777, 21818) to write a [host-exposures] entry for E2E_HOST_EXPOSURE_PORT into the client config.toml that minvmd reads. Until that change, the e2e host-exposure arm at port 18081 will fail. I did not run the e2e lane (it needs KVM). |
Parse the design's inline-table entry form, with proto defaulting to tcp, and drop the bare-port and range forms. Refuse at load an entry at a Minimal listener's port, and log the holder. The gate matches the frame protocol against the entry, and refuses a box or the cohort at the baseline set's alias ports and at listener ports. Move the retired-variable items above the pidfd doc comment, so the linux cfg gates signal_via_pidfd again and macOS builds.
There was a problem hiding this comment.
arch-review: CONDITIONS · 2 conditions · the full ruling
The gate now conforms to §7.1 for NET-156 and NET-158. The compare adds the { proto, port } entry form and refuses baseline and listener ports to box and cohort rows. But the change retires MINVMD_HOST_EXPOSURE_PORTS, and two consumers still use it. scripts/session-e2e.sh exports it to open port 18081 for the host-address box on VM lanes. At this head that fetch is refused, so the session proof fails. Write the port into the [host-exposures] table in C1. docs/reference/cli-minvmd.md still tells users that the variable opens ports. Replace that text in C2.
Ruled at 6b8a9eb against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.
|
Round 2 of 10 on a failed check suite, pushed as
Verify Review threads the coder judged:
On a VM host, a box reaches host.min.internal only at a port and protocol that a Notes from the agent: Round 2. The macOS build failure came from new items placed between the pidfd doc comment plus #[cfg(target_os = "linux")] and signal_via_pidfd. The items now sit above the doc comment, so the cfg gates the function again. This fixes all four E0425 errors. I did not build on macOS here, so the macOS lanes owe the evidence. The table now accepts only the inline-table form { proto, port } with proto defaulting to tcp. HostExposure carries the protocol, and the gate matches it. The load-time refusal covers the Minimal listener ports: 7654 (hostname proxy, a literal that mirrors cmd/run.rs NODE_DEFAULT_PROXY_PORT, which is private and outside the layers), 7656 (answerer) and 8118 (Box Egress Proxy). The baseline set's alias ports change when an entry is re-pointed, so the gate refuses a box or cohort there for each flow and does not refuse them at load. Outside the layers and not changed: scripts/session-e2e.sh (lines 396-406, 21777, 21818) still exports MINVMD_HOST_EXPOSURE_PORTS. It must write |
|
The runtime will not revise this pull request again on its own: the diff is +1208/-376 (1584 lines), past 3x the plan's estimate of 490 lines (est: 220 code, 270 tests, as #2689 reads now), at round 2 (the last trigger was a failed check suite). A person decides now. Remove |
|
/revise address review concerns and fix CI |
…ost-min-internal-are-refused-outside-t
current() now stats and reads config.toml with the lock released, so a slow disk does not stall other frames to host.min.internal. The gate applies the baseline ports to TCP alone, so the table decides a UDP exposure at the same port for the node row.
minvmd no longer reads MINVMD_HOST_EXPOSURE_PORTS, so the e2e writes the port into the hermetic client config.toml instead. The client config now accepts the [host-exposures] table, so min still parses a file that has it.
The reference still said MINVMD_HOST_EXPOSURE_PORTS opens ports. Describe the client config.toml table, its entry form, the reread on change, the listener ports it refuses and the warning for the retired variable.
There was a problem hiding this comment.
arch-review: CONDITIONS · 3 conditions · the full ruling
The host gate now holds host.min.internal default-deny and reads the [host-exposures] table live, as §7.1 lines 248 and 250 require. Three gaps remain. The listener exclusion is a fixed list, but the hostname proxy port can be a pin or a drawn fallback, and line 252 excludes any Minimal listener at each flow. Make the change in C1. The table does not refuse a baseline alias port at load, and the log does not name that holder. Make the change in C2. The in-VM relay still drops an own-address box at every alias port, but the reference and the PR body say that a box reaches the exposed ports. Correct the text and file the residual in C3.
Ruled at a460f4f against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.
|
Round 1 of 10 on a /revise note, pushed as
Verify Outside the task's
Converted to a draft: something above needs a person before review. Review threads the coder judged:
On a VM host, a flow from a box to host.min.internal reaches only a port that the [host-exposures] table of the client config.toml opens, under the box's egress rules. The node alone also reaches the baseline set's TCP port there. A person can add or remove a table entry, and the alias opens or closes that port with no restart. min still reads a config.toml that has the table. Notes from the agent: Revise round 1. In-layer fixes: HostExposures::current now stats and reads config.toml with the lock released. The gate applies the baseline alias ports to TCP alone, so the table decides a UDP exposure at the same port. The host_alias_admits_baseline_for_node_plane_only test now checks this case. Out-of-layer files, touched because the person asked to fix CI: (1) scripts/session-e2e.sh. The e2e failed at NET-003 because it still exported the retired MINVMD_HOST_EXPOSURE_PORTS. It now appends |
There was a problem hiding this comment.
arch-review: CONDITIONS · 1 condition · the full ruling
The gate now refuses host.min.internal outside the design's openings, so NET-156 and NET-158 conform. One gap has no owner. In the VM, the guest relay in minimald gets no exposure ports, so an own-address box that names the host is still refused at every port. Section 7.1 lines 250 and 253 say every box that declares the name reaches the exposure, on the own_ip lane too. The reference states the limit, but no issue owns it, and the PR body says the opposite. File the issue and name it in the three places that C1 lists.
Ruled at ad4086a against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.
|
/revise Deliver arch C1, using #2990 (T154) as the issue for the guest half of an own-address box's exposure. Make the three edits the ruling names: mark the sentence at |
…ost-min-internal-are-refused-outside-t
Link #2990 as the owner of the guest half in the reference and in the e2e comment beside E2E_HOST_EXPOSURE_PORT.
There was a problem hiding this comment.
arch-review: PASS · the full ruling
The head conforms to design §7.1 for NET-156 and NET-158. The host gate opens the alias only at the current table's exposures. The node-plane identity alone gets the baseline ports. The resolver and the Box Egress Proxy keep their own arms. The last commit marks the own-address refusal as a current limit and links #2990. I accept that as a filed residual.
Ruled at 24b733f against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.
|
Round 4 of 10 on a /revise note, pushed as
Verify Review threads the coder judged:
On a VM host, the gate admits at host.min.internal only the design's openings. A box reaches a host exposure only under the current [host-exposures] table and its own rules, and only the node reaches the node-plane baseline set there. The reference and the e2e comment now say that the refusal of an own-address box at every port is a current limit, and they link #2990 as its owner. Notes from the agent: Round 4 changed only docs and a comment. The code and the table reader did not change, because T148 owns the shared reader. I cannot edit the PR body. Use this text for note 3: "Residual: an own-address box in a VM still gets a refusal at every host.min.internal port, whatever the table says, because the guest relay (minimald compiled_egress) gets no exposure ports. Design section 7.1 lets such a box reach an exposure when it declares the name. #2990 (T154) tracks the guest half." just lint-prose did not run, because vale is not installed on this box. The e2e script passed bash -n. I did not run it. |
|
/revise Design decision on the MEDIUM security thread at |
An undeclared egress.allow_subnets allows every subnet but names nothing, so it no longer covers the host alias. A box with no egress section is refused at every host exposure (NET-160).
Spec lint
Advisory. None of this blocks the merge; the review decides. Advisory, and never a merge gate. Linter: |
There was a problem hiding this comment.
minimal-review — approval withheld
Verdict COMMENT — APPROVE withheld: 18 unresolved threads — minimal-arch[bot] crates/minvmd/src/net/host_exposures.rs, minimal-arch[bot] crates/minvmd/src/net/host_exposures.rs, minimal-arch[bot] crates/minvmd/src/net/egress_gate.rs, minimal-arch[bot] crates/minvmd/src/net.rs:829, minimal-arch[bot] crates/minvmd/src/net/host_exposures.rs, …. No findings.
Panel: 3 of 3 personas ran (correctness, security, supply-chain).
There was a problem hiding this comment.
arch-review: CONDITIONS · 3 conditions · the full ruling
The compare refuses an allow-all box at every host exposure. In this tree, an undeclared allow_subnets at the gate is the allow-all box (box_registry.rs:4694). That box still reaches its siblings. Section 7.1 line 251 says "an allow-all box reaches exposures as it reaches siblings", and architecture.md line 933 says the same. Revert the gate arm and its test in C1. Revert the NET-160 amendment in C2, because the spec follows the design. Remove the incorrect doc sentence in C3. The rest of the PR keeps the PASS on 24b733f7.
Ruled at 410fb54 against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.
|
Round 5 of 10 on a /revise note, pushed as
Verify Review threads the coder judged:
On a VM host, the gate admits at host.min.internal only the design's openings. A box row reaches a host exposure only when the current [host-exposures] table opens the port and the box names the host, by host.min.internal in egress.allow_dns_hosts or by a declare 10BC8 d egress.allow_subnets that covers the alias. A box with no egress section is refused at every exposure. Only the node reaches the node-plane baseline set at the alias. Notes from the agent: Revise round 5 follows the /revise note. AliasOpenings::for_box now uses is_some_and in place of is_none_or. host_alias_exposure_needs_the_box_to_name_the_host has a new case for a box with no egress section. Tests that expected a box with no egress section to reach an exposure now give that box an egress section that names host.min.internal, through a new test helper names_the_host(). The 'locked' box in host_alias_refused_outside_exposures_for_boxes also names the host now, so that its own deny rule refuses the flow, as the test intends. The host-address cohort row does not change. NET-160 now reads 'is undeclared or does not cover the alias', and its rationale comment no longer says an allow-all box reaches an exposure. cli-minvmd.md now says that a box with no egress section reaches no exposure. Vale is not installed on this box, so I did not run just lint-prose. I ran the full minvmd suite once: 688 passed and 2 stalled-switch timing tests failed near their 5 s limit. On a rerun, a_stalled_switch_does_not_hold_the_relay_past_a_guest_close passed 3 of 3 times with this change, and it also passed on the unchanged head. Neither of those tests sends to the host alias. The spec's second verify line (minimald native_own_ip_host_exposure_needs_the_box_to_name_the_host) names a test that does not exist in this tree, and minimald is outside this task's layers. |
|
/revise This replaces the design decision in the previous note: follow the arch ruling at |
…ost-min-internal-are-refused-outside-t
An undeclared egress.allow_subnets covers host.min.internal again, so a box with no egress section reaches an exposure as it reaches a sibling box (design 7.1). This reverts the gate change of 410fb54, restores the NET-160 text, and keeps the undeclared test case as an admit.
There was a problem hiding this comment.
minimal-review — approval withheld
Verdict COMMENT — APPROVE withheld: 22 unresolved threads — minimal-arch[bot] crates/minvmd/src/net/host_exposures.rs, minimal-arch[bot] crates/minvmd/src/net/host_exposures.rs, minimal-arch[bot] crates/minvmd/src/net/egress_gate.rs, minimal-arch[bot] crates/minvmd/src/net.rs:829, minimal-arch[bot] crates/minvmd/src/net/host_exposures.rs, …. No findings.
Panel: 3 of 3 personas ran (correctness, security, supply-chain).
There was a problem hiding this comment.
arch-review: PASS · the full ruling
This head conforms to the design. Section 7.1 line 251 says that an allow-all box reaches a host exposure as it reaches a sibling box. AliasOpenings::for_box now treats an undeclared allow_subnets as coverage of the alias again, and the test asserts that admit. The other hunks are the same as on 24b733f7, which got a PASS. Issue #2990 holds the own-address limit as a residual. The task names host_alias_refuses_a_port_removed_from_the_table, but that test is now host_exposure_removal_ends_established_flows. Update the task's test line.
Ruled at 37de4e1 against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.
|
Round 6 of 10 on a /revise note, pushed as
Verify Review threads the coder judged:
On a VM host, the gate admits at host.min.internal only the design's openings. A box reaches a host exposure only under the current [host-exposures] table and its own egress rules. A box with no egress section is allow-all and reaches an exposure as it reaches a sibling box. Only the node reaches the node-plane baseline set at the alias. Notes from the agent: Round 6 follows the arch ruling at 410fb54. The commit reverts the gate and spec changes of 410fb54: for_box uses is_none_or again, and the NET-160 text and rationale have their earlier wording. The test keeps the undeclared box case, and it now expects Ok(GateAdmit::Row). The revert also takes out the names_the_host() helper that 410fb54 added to other tests. Those tests pass without it, because an allow-all box covers the alias. In docs/reference/cli-minvmd.md only the sentence 'A box with no egress section reaches no exposure.' is gone. The rest of the current-limit sentence and the #2990 link stay. The earlier thread PRRT_kwDOPdfcxM6zfIEo now gets the verdict invalid, because the ruling replaces it. cargo clippy -p minvmd --all-targets is clean. I did not run just clippy-strict. |
There was a problem hiding this comment.
minimal-review — approved
Verdict APPROVE — every review thread is resolved; @norrietaylor closed the last one. The review of 37de4e147f93 withheld this approval on the threads open then. Posted as a comment: the App cannot approve its own pull request; the check and the label carry it.
There was a problem hiding this comment.
Reviewed 37de4e14 against T102's plan block. Flows to host.min.internal are refused outside the design's openings: the baseline ports open for the node row alone, Minimal's own listeners are refused at each flow ahead of the table, and a box must name the host or be covered by allow_subnets. An allow-all box reaches an exposure as it reaches a sibling, per the design. The table is reread on change and fails closed. An own-address box in a VM stays refused until #2990. Arch-review passes on this head.
…penings (#2871) T102. Behaviour change, for the release notes: host-address boxes reach only ports opened in [host-exposures]. Before this, a host-address box reached the host through host.min.internal at every port. Own-address boxes stay refused end to end: the host gate would admit a configured exposure under their rules, but the guest still refuses them. The egress gate refuses a flow to the host alias unless a port in the current [host-exposures] table of the client config.toml opens it, and the row's own rules then decide the flow. Each entry is { proto, port }, with proto defaulting to tcp. The node plane alone also reaches the ports its baseline endpoints open at the alias. minvmd rereads the table when the file changes, so an entry opens or closes its port without a restart. A missing or unparseable file fails closed. An entry at a Minimal listener's port is refused and logged. Each refused flow logs under the host-alias rule with the row and the port. This retires MINVMD_HOST_EXPOSURE_PORTS, and minvmd warns if it is still set. Backport of #2871. Lands after T103 here, as on main: its baseline and gate edits sit on that commit. T147 #2956, the project-file warning, stays out of 0.7.3. A project's [host-exposures] table still opens nothing, because the reader takes only the client file. Adapted to the line: - The client config lives in sessions/src/client/config.rs, and host_exposures joins it without main's [secrets] and [secret-store-rules] sections. - boxes:: reads sessions::, tests draw session ids from a v4 UUID, and DropLimiter stays in egress_gate.rs. (cherry picked from commit 964a71b)
…penings (#2871) T102. Behaviour change, for the release notes: host-address boxes reach only ports opened in [host-exposures]. Before this, a host-address box reached the host through host.min.internal at every port. Own-address boxes stay refused end to end: the host gate would admit a configured exposure under their rules, but the guest still refuses them. The egress gate refuses a flow to the host alias unless a port in the current [host-exposures] table of the client config.toml opens it, and the row's own rules then decide the flow. Each entry is { proto, port }, with proto defaulting to tcp. The node plane alone also reaches the ports its baseline endpoints open at the alias. minvmd rereads the table when the file changes, so an entry opens or closes its port without a restart. A missing or unparseable file fails closed. An entry at a Minimal listener's port is refused and logged. Each refused flow logs under the host-alias rule with the row and the port. This retires MINVMD_HOST_EXPOSURE_PORTS, and minvmd warns if it is still set. Backport of #2871. Lands after T103 here, as on main: its baseline and gate edits sit on that commit. T147 #2956, the project-file warning, stays out of 0.7.3. A project's [host-exposures] table still opens nothing, because the reader takes only the client file. Adapted to the line: - The client config lives in sessions/src/client/config.rs, and host_exposures joins it without main's [secrets] and [secret-store-rules] sections. - boxes:: reads sessions::, tests draw session ids from a v4 UUID, and DropLimiter stays in egress_gate.rs. (cherry picked from commit 964a71b)
T102 — Flows to host.min.internal are refused outside the design's openings
slice: S21
kind: task
covers: NET-156, NET-158
size: M
est: 220 code, 270 tests
executor: agent
difficulty: needs care
model: standard
diagnostics:
min bugbundles carry the daemon log of minvmd, where each refused flow to the host alias records the row (node, cohort or box), the destination port and the rulehost-aliasobservability: the egress gate's rate-limited drop line names the host-alias rule and the row for every refused flow, logged by minvmd on the host
layers:
- gate:
- crates/minvmd/src/net/egress_gate.rs:
infrastructure_destinationadmits at the host alias only the design's openings: a configured host exposure (a port the current[host-exposures]table of the clientconfig.tomlopens, per the gominimal/arch#110 ruling in networking design v0.9.2 §7.1), the gateway resolver's port, a credentialed-lane box's steered flow to the node-local Box Egress Proxy, and, for the node-plane identity alone, the node-plane baseline set; a box row or the host-address cohort never gets the baseline set- crates/minvmd/src/net/baseline.rs: exposes the baseline set's endpoints at the alias as a port set the gate reads for the node-plane identity only, and retires
MINVMD_HOST_EXPOSURE_PORTSand its reader- crates/minvmd/src/net/host_exposures.rs (new): reads the
[host-exposures]table of the clientconfig.tomland reads it again when the file changes, asbep servedoes for[secret-store-rules], so the gate decides each flow by the current table: an entry added opens its port, an entry removed closes it- crates/minvmd/src/net.rs: hands the gate the current table in place of
host_exposure_ports_from_envtest: cargo nextest run -p minvmd host_alias_refused_outside_exposures_for_boxes
test: cargo nextest run -p minvmd host_alias_admits_baseline_for_node_plane_only
test: cargo nextest run -p minvmd baseline_rules_admit_the_enumerated_categories_alone
test: cargo nextest run -p minvmd host_alias_admits_a_port_the_current_table_opens
test: cargo nextest run -p minvmd host_alias_refuses_a_port_removed_from_the_table
produces: none
consumes: none
proof: On a VM host, a flow from any box to
host.min.internalreaches only the design's openings, and the node alone also reaches the node-plane baseline set there.Generated by Claude Code
Proof
On a VM host, the host-side egress gate in minvmd now refuses every flow from a box to host.min.internal unless a port in the current [host-exposures] table of the client config.toml opens it, and the box's own egress rules then decide that flow. The node alone also reaches the ports that its baseline set's own endpoints open at the alias. Each refused flow logs a rate-limited line with rule_matched="host-alias", the row (node, cohort or box) and the port. A person who edits the table sees an added entry open its port and a removed entry close it within about a second, without a restart. This is proved by unit tests at the gate. No VM run was made on this box.
Notes from the agent: 1. The spec excerpt does not contain NET-156 or NET-158. Its highest ID is NET-155. I worked from the task's layers and from the nearby spec text: NET-003, the four port-scoped openings under NET-134, and the design-reasoning paragraph on host.min.internal.
2. The resolver opening and the Box Egress Proxy opening are not at the alias address. The resolver is on the gateway, and the spec says the proxy's steered destination is its own address. The gate keeps both openings at their own arms. At the alias, port 53 and port 8118 are refused like any other port. host_alias_refused_outside_exposures_for_boxes pins this, and also pins that both openings still pass at their own addresses.
3. Behaviour change: an own-address box now reaches a configured exposure, under its own rules. Before this change, every port refused it. A deny-all box still reaches nothing.
4. Table format, which this task defines: in [host-exposures], each key is a name and each value is an integer port, or a string with a port or a 'lo-hi' range. A bad entry is skipped and logged. A missing or unparseable file fails closed, with no exposure. Only the user-level config.toml in the default config dir is read, the same file bep serve reads. A min --config-dir override and /etc/minimal/config.toml are not read. Th…[truncated]
Tests
cargo nextest run -p minvmd host_alias_refused_outside_exposures_for_boxescargo nextest run -p minvmd host_alias_admits_baseline_for_node_plane_onlycargo nextest run -p minvmd baseline_rules_admit_the_enumerated_categories_alonecargo nextest run -p minvmd host_alias_admits_a_port_the_current_table_openscargo nextest run -p minvmd host_alias_refuses_a_port_removed_from_the_tableVerify
min task run agent-verifypassed ([tasks.verify]).Closes #2689