Repository navigation
feat(minvmd): decide the node plane's own traffic by its baseline set - #2723
norrietaylor merged 37 commits into
Conversation
The registry and cache baseline entries now name the hosts the daemon's own fetches go to. The node plane's own lookups pin the addresses those names resolve to, under the same window and cap as a box's pins, and every pin and re-pin is logged. The baseline phase ships in force: a frame from the node's own address reaches a pinned address, a subnet entry, the resolver, the host alias at a configured exposure and a resident box's lease, and nothing else. A refused frame logs a rate-limited line naming the drop for want of a pin. Covers NET-085 and NET-130 (T103).
min session policy now shows the baseline set as what bounds Minimal's own downloads in the VM, with each entry naming the host those downloads go to (NET-130, T103).
|
Important Review skippedBot user detected. To trigger a single review, invoke the
|
|
/revise Fix the test-kvm timeout. The last round failed inside the runtime on a
Generated by Claude Code |
…-node-plane-s-own-traffic-by-its-basel
A box launch inside the VM first git-fetches its project's package source repository (github.com/gominimal/pkgs) through the in-VM daemon. The baseline named cache.minimal.dev for the registry, so the in-force gate dropped that fetch for want of a pin and the launch hung. The registry entry now names github.com. Its lookups pin against the registry entry. The cache entry keeps cache.minimal.dev, where the package index and content live (NET-130, T103).
The baseline's registry entry now names the package source host, so the policy view's registry row and JSON entry name github.com (NET-130, T103).
|
Round 2 of 10 on a /revise note, pushed as
Verify Outside the task's
Converted to a draft: something above needs a person before review. On a VM host, the in-VM daemon's own traffic reaches the package source repository's host (github.com, the registry entry) and the cache (cache.minimal.dev), each only at the addresses its own lookups pinned, and nothing else outside the resident boxes' rules. Notes from the agent: Revise round 2. I was not able to read the boot-daemon log artifact (no gh or network credential here), and this box has no /dev/kvm. So I did not run |
|
Round 3 of 10 on a failed check suite: the coder changed nothing. Round 3 changed nothing, on purpose. The three failed checks do not need a code change on this branch that I can make. (1) commitlint: commit 5b2e973 has a body line of 146 characters, and the limit is 100 (body-max-line-length). The only fix is to reword that commit. The rules forbid me to rewrite commits or force-push. A person can fix it with a squash merge that uses a short body, or with a reword and a force-push of 5b2e973. (2) cargo-deny: the advisory is 'Exponential event growth with ENABLE_WIKILINKS', in pulldown-cmark 0.13.4. Cargo.lock and deny.toml on this branch are the same as on origin/main. This branch does not cause the failure, and main has the same failure. The fix (update pulldown-cmark, or ignore the advisory in deny.toml) is outside this task's layers and must be a separate pull request. The log also shows a missing musl toolchain line, which is a CI image problem, not this branch. (3) ci-success is the aggregator. It fails because the two checks abov…[truncated] |
|
Round 4 of 10 on a failed check suite: the coder changed nothing. No code change this round. None of the failed checks points to code inside this task's layers. (1) commitlint: the body of the existing commit 5b2e973 has a line longer than 100 characters. Only a rewrite of that commit can fix it, and the revise rules forbid a rewrite or a force-push. A person must reword it before the merge, or merge with a squash that uses a new message. (2) cargo-deny: the advisory is on pulldown-cmark ('Exponential event growth with ENABLE_WIKILINKS'). This branch does not change Cargo.lock or deny.toml, so the failure is also on main, and the fix is outside the layers. (3) test-kvm: the job excerpt shows only script source lines and no failing test or error output. I was not able to find the failure, and this box has no /dev/kvm to reproduce it. ci-success and ci-linux-kvm-success are aggregators that fail because of the checks above. All five test lines pass at 5b2e973. Deferred: cargo nextest run -p minvmd vm_escape_bounded_to_resident_union (lane…[truncated] |
5b2e973 to
1b3d1e0
Compare
|
/revise Fix the e2e failure on test-kvm at 5b2e973 (job 122751751056; the tree is unchanged at 1b3d1e0, which only rewords a commit body for commitlint). Change nothing else.
Generated by Claude Code |
|
Revise round 4 ran against |
|
/revise (Re-post: round 5 failed inside the runtime on a
Generated by Claude Code |
|
Round 7 of 10 on a failed check suite: the coder changed nothing. Revise round 7: no code change, on purpose. The fix needs a planning decision, and it is outside this task's layers. Failed checks:
|
|
/revise The owner ruled on the planning question from round 7: gate by cgroup, not by address. Change only what this needs.
Generated by Claude Code |
…-node-plane-s-own-traffic-by-its-basel
A host-address box, the default network mode, sends from the node address, so the in-force baseline judged its egress as node traffic and dropped it (NET-107). The guest classifier now writes the cohort ct mark into the IPv4 DSCP field: BOX_COHORT_DSCP for a connection a box opened, and zero on every other packet. The host gate judges a marked frame at the node address by the node row and the box's classifier leaf, and an unmarked one by the baseline set, so a guest without the table fails closed.
…-node-plane-s-own-traffic-by-its-basel
|
Round 8 of 10 on a /revise note, pushed as
Verify Outside the task's
Converted to a draft: something above needs a person before review. On a VM host, a process inside the VM that sends from the node's own address and is not in a box's cgroup reaches only the registry and the cache that Minimal downloads from: an unmarked frame from that address is judged by the baseline set. A host-address box (the default session) also sends from that address. Its frames now carry the guest classifier's box mark, so the gate judges them by the box's own rules and the session keeps its outbound network. Notes from the agent: Round 8: gate by cgroup, not by address, as the owner ruled. The guest classifier already gives each new connection a ct mark from the sender's cgroup (the cohort bit for the boxes subtree, the node bit for the rest of the slice). That mark does not leave the VM. So on the guest only, the classifier script now renders a postrouting filter chain, mark_cohort. It sets the IPv4 DSCP field to boxes::core::egress::BOX_COHORT_DSCP (55, RFC 2474 pool 2) on packets of a cohort-marked connection, and clears DSCP on every other packet. Thus no process outside the boxes cohort can wear the mark. FrameSummary now reads DSCP. In gate_verdict, the in-force node-plane arm takes only frames from the node address that do not carry the mark. A marked frame falls through to the node row, and the box's classifier leaf in the guest decides it. The reply, control-surface, infrastructure and proxy-lane floors still apply to it. A guest whose classifier table never loaded sends no marks, so its traffic fails closed to the baseline set. A native host install takes no --cohort-dscp and renders no DSCP chain. Layer exception (put this in the PR body): the fix needs three files outside T103's layers. crates/boxes/src/core/egress.rs holds the shared BOX_COHORT_DSCP constant and the DSCP read in FrameSummary. crates/minimald/src/net/classifier.rs passes --cohort-dscp in the guest render and updates its argv and digest tests. scripts/install-host-classifier.sh adds the --cohort-dscp option and the mark_cohort ch…[truncated] |
|
/revise Fix the two failures on ee7e4dc (round 8, gate by cgroup). No round started on its own for this failed suite. Change nothing else.
Generated by Claude Code |
|
That Nothing to type again: the command gets a rocket when its round starts, or a refusal that names the reason. No round was counted for it yet. |
…-node-plane-s-own-traffic-by-its-basel
shellcheck 0.9.0 on the CI runner flags the old form as SC2015. The accepted range stays 1 to 63.
The guest renders its table with --cohort-dscp, so the native render the KVM harness compares its load digest against must name the same flag.
|
The runtime will not revise this pull request again on its own: round 11 would exceed the cap of 10; 0 base merges were not counted (the last trigger was a failed check suite). A person decides now. Remove |
|
Round 10 of 10 on a /revise note, pushed as
Verify Outside the task's
Converted to a draft: something above needs a person before review. On a VM host, a process inside the VM that sends from the node's own address reaches the registry (github.com) and the cache (cache.minimal.dev) that Minimal downloads from, and nothing else outside the resident boxes' rules. A box frame that the guest classifier marks is still decided by the box's rules. This round makes the shellcheck gate and the KVM digest check agree with that change. Notes from the agent: Revise round 10. Two fixes, and nothing else changed.
The macOS and aggregator failures only follow the KVM lane (await-kvm). Deferred: cargo nextest run -p minvm…[truncated] |
|
Round 1 of 10 on the runtime's review findings: the coder changed nothing. Revise round 1: the one unresolved thread is judged invalid for this pull request, so this round changes no code and runs no tests. The gap that the thread names is real, but this branch already documents it as a residual. The doc comments on HostIpResidency::keep in crates/minimal/src/cmd/boxes/vm_host.rs and on BoxRegistry::admit_host_ip_box in crates/minvmd/src/box_registry.rs name the cases, and issue #2747 owns them. The earlier threads PRRT_kwDOPdfcxM6x6vZh and PRRT_kwDOPdfcxM6x6vZt got the same verdict. A daemon-side bound needs a new liveness signal: a host_ip box has no row in the VM host daemon's registry, so the daemon cannot check its residents against live boxes. Neither file is in this task's layers. The deferred test vm_escape_bounded_to_resident_union (lane: vm) did not run on this box. Review threads the coder judged:
|
There was a problem hiding this comment.
arch-review: PASS_WITH_CONDITIONS · 2 conditions · the full ruling
This compare conforms to the design. An equal re-admit keeps the cohort pins, the gate asks the shared pins once, and a bare IPv4 baseline term narrows the set. The VM escape leg ran at 8206300b, and this compare changes the code that the leg drives. Re-run it at this head and record the job (C1). Amend the plan's T103 layers: to list the out-of-layers files (C2).
Ruled at 49e8878 against gominimal/arch@42d214dc8ac8 by claude-opus-5-5.
There was a problem hiding this comment.
Own review at 49e8878. The only change since 5c6dba0 is the round-10 commit fix(minvmd): keep the host_ip cohort record on an equal re-admit.
admit_host_ip_boxnow returns before it touches the set when the held rules equal the new ones.HostIpBoxRulesderivesPartialEq/Eq. The cohortArctherefore stays, and so do the other boxes' pins and flows. That closes 4422402614.- The doc comment states that a real change to the set still rebuilds the entry.
host_ip_verdictasks the pins once, after the loop, so a frame counts once (4422402619). An earlier admit by another box's own rules still returns first.baseline_endpoints_from_rawskips a bare IPv4 term with a warning that names<term>/32(4410130045). This only narrows the set.
I see no design change. My arch verdict is PASS at this head, and C1 stays carried as residual #2747. Still open: security thread 4423599090, which waits on the owner's ruling, and the macOS lane, which waits on #2757.
Generated by Claude Code
There was a problem hiding this comment.
arch-review: PASS_WITH_CONDITIONS · 2 conditions · the full ruling
This head merges main cleanly, and the PR's own diff did not change since 49e88786. The ruling on that head stands. The node plane is decided by the in-force baseline set, as section 5.1 and section 4.3 rule 2 require. Two items stay open as filed residuals. C1 is #2747: the gate's union can keep a host_ip box that is not live, and section 8 bounds the union to resident boxes. C2 is gominimal/arch#153: the design must decide if github.com is a baseline endpoint.
Ruled at 9bf4c0d against gominimal/arch@42d214dc8ac8 by claude-opus-5-5.
There was a problem hiding this comment.
Babysitter approval at 9bf4c0d: every merge gate passes at this head (arch PASS at 9bf4c0d, zero open threads, every lane aggregator green). Squash title feat(minvmd): decide the node plane's own traffic by its baseline set and the squashed message pass commitlint. Queued for squash merge.
Generated by Claude Code
…-node-plane-s-own-traffic-by-its-basel # Conflicts: # crates/minimal-client/src/box_registration.rs
There was a problem hiding this comment.
arch-review: PASS_WITH_CONDITIONS · 1 condition · the full ruling
The merge of main is clean, and it does not change the PR's own work. The host_ip verbs stay on the host door, and main's MintBoxId stays on the guest door. One condition is open. The deferred VM leg vm_escape_bounded_to_resident_union ran at 49e88786, not at this head. Two merges since then changed crates/minvmd/src/control.rs, and the leg drives that file. Section 8 line 310 bounds the escape to the resident union plus the baseline set. Only that leg proves the bound. Run the leg at ca1dbf76 and record the job in the PR body, as C1 says.
Ruled at ca1dbf7 against gominimal/arch@42d214dc8ac8 by claude-opus-5-5.
|
A merge of main before round 2, not counted against the cap (1 of 3), pushed as Resolved conflicts in:
Verify Outside the task's
Converted to a draft: something above needs a person before review. On a VM host, a process inside the VM that sends from the node's own address reaches the registry and the cache Minimal downloads from, and nothing else outside the resident boxes' rules. This round only merges main into the branch, and the node-plane tests still pass after the merge. Notes from the agent: Conflict round. The one conflict was in crates/minimal-client/src/box_registration.rs. The branch added a BoxControlReply::HostIpBox arm and main added a BoxControlReply::BoxIdMinted arm in the same place. I kept both arms and committed the merge. That file is outside this task's layers, but the merge put it in the diff. cargo clippy -p minimal-client -p minvmd -p minimal --all-targets is clean after the merge. Deferred: cargo nextest run -p minvmd vm_escape_bounded_to_resident_union (lane: vm). I did not run it. |
|
The runtime will not revise this pull request again on its own: the diff is +3149/-629 (3778 lines), past 3x the plan's estimate of 860 lines (est: 400 code, 460 tests, as #2690 reads now), at round 2 (the last trigger was a conflict with the base branch). A person decides now. Remove |
There was a problem hiding this comment.
Approve at ca1dbf76.
- This head adds one conflict-resolving merge of
main(#2759) to9bf4c0da. The PR's own diff is unchanged. Incrates/minimal-client/src/box_registration.rs, the resolution keeps both newBoxControlReplyarms: this PR'sHostIpBoxand main'sBoxIdMinted. Each arm refuses the reply as an answer to the registration. - The merge changed
crates/minvmd/src/control.rs, which serves the verbs the VM leg drives. So arch ruling 614 asked for the leg to run again.vm_escape_bounded_to_resident_unionpassed on the KVM lane at this head in 77.9 s (job 124314615395, 39/39), and the PR body records that run. - Arch ruling 614 rules PASS_WITH_CONDITIONS, and its one condition is met. The residuals it carries are filed: #2747, and gominimal/arch#153. Every lane is green.
Generated by Claude Code
…2723) T103. The node-plane baseline ships in force. The registry and cache entries name the hosts the daemon's own fetches go to (github.com, cache.minimal.dev), and the node plane's own lookups pin the addresses they resolve to, under a box's window and cap. A frame from the node's own address reaches a pinned address at TCP 443, a subnet entry, the resolver, the host alias at a configured exposure and a resident box's lease, and nothing else. MINVMD_BASELINE_REGISTRY and MINVMD_BASELINE_CACHE re-point the entries. A host-address box sends from the node address too, so the guest classifier writes a box mark into the IPv4 DSCP field. The host gate decides a marked frame by the union of the resident host_ip boxes' rules and drops it when none is resident. The client admits a host_ip box's egress over new host-door verbs before the box runs anything, re-admits it at attach and exec, and retires it when the session goes. Backport of #2723. Lands before T102 here, as on main: T102 edits this commit's baseline and gate code, and its baseline.rs does not apply without it. Adapted to the line: - cmd/boxes/create.rs and vm_host.rs hunks land in cmd/session.rs, dash/rpc.rs in minimal-tui, and the task hunks use the line's config variable. - host_ip_box_with_vm_host and HostIpResidency live in minimal-client's box_registration, since minimal-tui is its own crate here and cannot reach minimal's. minimal-tui reads the record through a new session_record_at there, and its activation admits no declared egress because its policy is the default. - The activation's failure path has no cleanup outcome, so the guard retires on every failed finalize. Main keeps the rules when the destroy may have failed. - DropLimiter stays in egress_gate.rs, since the line has no drop_limiter module. - The line has no ProxyListening or BoxIdMinted replies, and no SessionId::mint, so tests draw their ids from a v4 UUID. boxes:: reads sessions:: throughout. (cherry picked from commit 486ea13)
…2723) T103. The node-plane baseline ships in force. The registry and cache entries name the hosts the daemon's own fetches go to (github.com, cache.minimal.dev), and the node plane's own lookups pin the addresses they resolve to, under a box's window and cap. A frame from the node's own address reaches a pinned address at TCP 443, a subnet entry, the resolver, the host alias at a configured exposure and a resident box's lease, and nothing else. MINVMD_BASELINE_REGISTRY and MINVMD_BASELINE_CACHE re-point the entries. A host-address box sends from the node address too, so the guest classifier writes a box mark into the IPv4 DSCP field. The host gate decides a marked frame by the union of the resident host_ip boxes' rules and drops it when none is resident. The client admits a host_ip box's egress over new host-door verbs before the box runs anything, re-admits it at attach and exec, and retires it when the session goes. Backport of #2723. Lands before T102 here, as on main: T102 edits this commit's baseline and gate code, and its baseline.rs does not apply without it. Adapted to the line: - cmd/boxes/create.rs and vm_host.rs hunks land in cmd/session.rs, dash/rpc.rs in minimal-tui, and the task hunks use the line's config variable. - host_ip_box_with_vm_host and HostIpResidency live in minimal-client's box_registration, since minimal-tui is its own crate here and cannot reach minimal's. minimal-tui reads the record through a new session_record_at there, and its activation admits no declared egress because its policy is the default. - The activation's failure path has no cleanup outcome, so the guard retires on every failed finalize. Main keeps the rules when the destroy may have failed. - DropLimiter stays in egress_gate.rs, since the line has no drop_limiter module. - The line has no ProxyListening or BoxIdMinted replies, and no SessionId::mint, so tests draw their ids from a v4 UUID. boxes:: reads sessions:: throughout. (cherry picked from commit 486ea13)
T103 — Decide the node plane's own traffic by its baseline set
slice: S16
kind: task
covers: NET-085, NET-130
size: L
est: 400 code, 460 tests
executor: agent
difficulty: needs care
model: strongest
diagnostics: the diagnostics bundle's VM host daemon log carries the start-up line that names the baseline phase as in force and lists each entry with the addresses it pinned
observability: the VM host daemon logs one line per baseline entry pinned or re-pinned, with the entry, the addresses and the source of the lookup, and its rate-limited drop line names a node-plane frame refused for want of a pin
layers:
- pins:
- crates/minvmd/src/net/baseline.rs: the registry and cache entries become named endpoints taken from the daemon's configured registry and cache, never absent and every change logged; the shipped phase becomes in force, and the posture the policy row and the start-up line spell drops the interim wording
- crates/minvmd/src/net/dns_pins.rs: the node plane's own lookups for a baseline entry pin the addresses they resolved to against that entry, under the same expiry and cap as a box's pins
test: cargo nextest run -p minvmd baseline_entry_pins_the_daemon_lookup_answers
- gate:
- crates/minvmd/src/net/egress_gate.rs: a frame from the node's own address is admitted only to an address a baseline entry pinned, the host alias and the resolver carve-out; the announced-arm tests move to the in-force arm
test: cargo nextest run -p minvmd unenrolled_baseline_set_from_helper_enumeration
test: cargo nextest run -p minvmd baseline_enumeration_always_carries_registry_and_cache
test: cargo nextest run -p minvmd node_plane_frame_admitted_only_to_pinned_baseline_address
test (deferred: vm): cargo nextest run -p minvmd vm_escape_bounded_to_resident_union
- policy:
- crates/minimal/tests/cli.rs: the policy assertion reads the in-force posture beside the baseline set, so
min session policyshows the baseline set as what bounds Minimal's own downloads in the VMtest: cargo nextest run -p minimal policy_shows_baseline_set
produces: none
consumes: none
proof: On a VM host, a process inside the VM that sends from the node's own address reaches the registry and the cache Minimal downloads from, and nothing else outside the resident boxes' rules.
Proof
On a VM host the gate decides every unmarked frame from the node's own address by the node-plane baseline set, and that set is in force. Such a frame reaches the registry (
github.com) and the cache (cache.minimal.dev) only at the addresses the node's own lookups pinned, and only on TCP 443. It also reaches the resolver, the host alias at a configured exposure and a resident box's lease. A frame that carries the box mark is decided by the union of the residenthost_ipboxes' rules, and dropped when nohost_ipbox is resident. Any other destination is dropped with a rate-limited egress-node-plane-unpinned line.min session policyshows the set asin force, with the rowsregistry github.comandcache cache.minimal.dev.Out of layers: these files carried product code outside T103's
layers:. arch_rulings 600 C2 asked the plan entry on #2690 to list them; arch_rulings 602 dropped that condition, and the owner kept the plan as it is. They are here because §7.1 makes the host-side creator own eachhost_ipbox's rules and the gate needs them:crates/minvmd/src/box_registry.rs(admit_host_ip_box,retire_host_ip_box),crates/minvmd/src/control.rs,crates/minimald-rpc/src/lib.rs,crates/minimald/src/rpc.rs,crates/minimal-client/src/attach.rs,crates/minimal-client/src/box_registration.rs,crates/minimal/src/cmd/boxes/create.rs,crates/minimal/src/cmd/boxes/vm_host.rs,crates/minimal/src/dash/rpc.rsandcrates/minimal/src/task.rs. Thebox_registry.rsandegress_gate.rstests that sent a node frame to 10.1.2.3 now send a resolver query instead.Design decisions:
(1) The registry entry defaults to
github.com(DAEMON_REGISTRY_ENDPOINT), where the in-VM daemon fetches package sources, and the cache entry tocache.minimal.dev(DAEMON_CACHE_ENDPOINT), the host of mctx's DEFAULT_REMOTE_CACHE_READ_URL.MINVMD_BASELINE_REGISTRYandMINVMD_BASELINE_CACHEre-point each category within itself, throughwith_registryandwith_cache, which log each change. Whethergithub.combelongs in the baseline is gominimal/arch#153.Tests
cargo nextest run -p minvmd baseline_entry_pins_the_daemon_lookup_answerscargo nextest run -p minvmd unenrolled_baseline_set_from_helper_enumerationcargo nextest run -p minvmd baseline_enumeration_always_carries_registry_and_cachecargo nextest run -p minvmd node_plane_frame_admitted_only_to_pinned_baseline_addresscargo nextest run -p minimal policy_shows_baseline_setVerify
min task run agent-verifyexited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.Deferred
The plan marked these test lines deferred: the executor lacks the lane, so nothing here ran them and they still owe evidence.
cargo nextest run -p minvmd vm_escape_bounded_to_resident_unionMINVMD_E2E=1, PASS in 77.9 s (job 124314615395, 39/39 passed)Out of scope
These files are outside the task's
layers:, so this PR is a draft:crates/minvmd/src/box_registry.rsResidual
host_ipbox's rules stay in the VM gate's union after the in-VM daemon's reap or a failed retire, until the VM host daemon restarts (arch C3 at 386a955, C2 at 8206300).github.combelongs in the node-plane baseline.Closes #2690
Generated by Claude Code