Repository navigation
Description
Task
| slice | size | difficulty | model | who | estimated lines |
|---|---|---|---|---|---|
| Escape into the VM reaches only the declared union | L | needs care | strongest | an agent | 1000 (520 code, 480 tests) |
On a VM-backed host, a box that allows github.com by name reaches the addresses its own lookup returned and nothing else, decided outside the VM: with the in-VM relay replaced by a hostile one that carries the box's frames to any subnet, the host gate drops every destination the box's own answers did not pin, refuses an answer that resolved into the row's deny set or the infrastructure set, keeps applying every non-DNS rule to the row, and the bundle's host log shows the decision was the host's; the NET-068 toolchain harness (
hostname_allowlist_toolchain_completes, #1771) still passes unchanged as the regression oracle for host-side resolution.
Covers:
- NET-081
- NET-066
- NET-067
Layers:
- vmhost:
- crates/minvmd/src/net/dns_pins.rs (new): the host-side admission table, one per registered box, filled from the DNS replies the host switch returns toward the box's switch address for a name the box's row declares in allow_dns_hosts, so every pin is an answer the box's own query received and never a second resolution; the answers pass the sessions crate's rebinding intersection against the row's deny_subnets and the infrastructure deny set before entry; the window, the per-name address cap, the TTL floor and cap and the used-pin retention past the window are the ones the in-VM gate applies, read from the sessions crate so both legs hold one number; the table is bounded per box and dropped with the row
- crates/minvmd/src/net/egress_gate.rs: the deferral arm is gone; a frame a DNS-resolving row would drop as an undeclared destination is decided on the host against that box's admission table, admitted when a live pin names the destination and dropped otherwise, with the in-VM pin table left as the precision copy; a DNS reply the switch returns toward a box is passed to the table on its way to the guest; the row's deny set, the infrastructure deny set, the protocol rule, the control surface and the unknown-source and undeclared-port drops are never deferred for any row
- crates/minvmd/src/net.rs: hands the gate each registered row's allow_dns_hosts with the row and wires the table's lifetime to the row's
cargo nextest run -p minvmd dns_box_undeclared_destination_decided_on_hostcargo nextest run -p minvmd host_pins_are_the_answers_the_box_receivedcargo nextest run -p minvmd host_window_refuses_denied_and_infrastructure_rangescargo nextest run -p minvmd hostile_relay_reaches_only_the_pinned_answerscargo nextest run -p minvmd host_side_rules_applied_outside_vm
- daemon:
- crates/minimald/src/net/dns_gate.rs: the module doc names the host-side table as the deciding copy on a VM-backed host and this one as the precision copy inside the VM; no behaviour change
cargo nextest run -p minimald dns_pinned_admission_window
Waits on:
- T63 — Gate the switch's publish verbs on the host and hand the node its ports #1708
- T66 — Register a box on the host and hand it its addresses #1711
Plan block
### T74 — Decide a DNS box's destinations on the host from the answers its own queries received
slice: S9
kind: task
covers: NET-081, NET-066, NET-067
size: L
est: 520 code, 480 tests
executor: agent
difficulty: needs care
model: strongest
diagnostics: the bundle's VM host daemon log tail carries one line per box when its host-side admission table is first filled and one rate-limited line per refused resolved range, so a DNS box whose destination was decided on the host reads so in a bundle
observability: one info log line per box at the first host-side pin, naming the box and the name; one rate-limited warn line per refused answer in the shared refusal format; a counter of frames the host gate admitted by a pin and refused for want of one
layers:
- vmhost:
- crates/minvmd/src/net/dns_pins.rs (new): the host-side admission table, one per registered box, filled from the DNS replies the host switch returns toward the box's switch address for a name the box's row declares in allow_dns_hosts, so every pin is an answer the box's own query received and never a second resolution; the answers pass the sessions crate's rebinding intersection against the row's deny_subnets and the infrastructure deny set before entry; the window, the per-name address cap, the TTL floor and cap and the used-pin retention past the window are the ones the in-VM gate applies, read from the sessions crate so both legs hold one number; the table is bounded per box and dropped with the row
- crates/minvmd/src/net/egress_gate.rs: the deferral arm is gone; a frame a DNS-resolving row would drop as an undeclared destination is decided on the host against that box's admission table, admitted when a live pin names the destination and dropped otherwise, with the in-VM pin table left as the precision copy; a DNS reply the switch returns toward a box is passed to the table on its way to the guest; the row's deny set, the infrastructure deny set, the protocol rule, the control surface and the unknown-source and undeclared-port drops are never deferred for any row
- crates/minvmd/src/net.rs: hands the gate each registered row's allow_dns_hosts with the row and wires the table's lifetime to the row's
test: cargo nextest run -p minvmd dns_box_undeclared_destination_decided_on_host
test: cargo nextest run -p minvmd host_pins_are_the_answers_the_box_received
test: cargo nextest run -p minvmd host_window_refuses_denied_and_infrastructure_ranges
test: cargo nextest run -p minvmd hostile_relay_reaches_only_the_pinned_answers
test: cargo nextest run -p minvmd host_side_rules_applied_outside_vm
- daemon:
- crates/minimald/src/net/dns_gate.rs: the module doc names the host-side table as the deciding copy on a VM-backed host and this one as the precision copy inside the VM; no behaviour change
test: cargo nextest run -p minimald dns_pinned_admission_window
produces:
- crates/minvmd/src/net/dns_pins.rs
consumes:
- crates/minvmd/src/net/egress_gate.rs
- crates/sessions/src/core/egress.rs
- crates/minimald/src/net/dns_gate.rs
proof: On a VM-backed host, a box that allows github.com by name reaches the addresses its own lookup returned and nothing else, decided outside the VM: with the in-VM relay replaced by a hostile one that carries the box's frames to any subnet, the host gate drops every destination the box's own answers did not pin, refuses an answer that resolved into the row's deny set or the infrastructure set, keeps applying every non-DNS rule to the row, and the bundle's host log shows the decision was the host's; the NET-068 toolchain harness (`hostname_allowlist_toolchain_completes`, gominimal/minimal#1771) still passes unchanged as the regression oracle for host-side resolution.
depends on:
- blocked by #1708
- blocked by #1711
Design ruling
Filed from the architecture review of #1788 (T63): the host gate defers an undeclared-destination drop to the in-VM gate for every row that resolves names, so a DNS-using box's destination rule is decided only inside the VM and the post-escape floor for such a box is any subnet; NET-081 is not met for name-resolving rows until this task lands. Two alternatives are ruled out: an admission set compiled on the host from allow_dns_hosts by resolving the names again (a second resolution yields different answers; the pins must be the addresses the box's own query received, design §5.3), and pins pushed up from the guest (a guest-asserted admission is what the host gate exists to refuse). The host switch is the box's resolver, so the replies it returns toward the box are the box's own answers; the host pins from those on their way in. Until this lands, the deferral arm in the gate stays exactly as narrow as it is and every other drop class is applied host-side for a DNS row, with one info line per such row saying the destination rule is deferred to the guest.
Activity
test(minvmd): prove a name-resolving row keeps every other host-side …
minimal-foundry commentedon Oct 2, 2026
Done. #1811 merged.
branch
agent/1808-decide-a-dns-box-s-destinations-on-the-host-from· epic #1437 · PR #1811Updated 2026-10-28T06:17:32.453Z.
T63 — Gate the switch's publish verbs on the host and hand the node i…