8000 fix: 0.7.3 backports by norrietaylor · Pull Request #3019 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

fix: 0.7.3 backports - #3019

Merged
norrietaylor merged 34 commits into
release/0.7.xfrom
fix/0.7.3-backports
Nov 5, 2026
Merged

norrietaylor merged 34 commits into
release/0.7.xfrom
fix/0.7.3-backports

Conversation

@norrietaylor
@norrietaylor norrietaylor commented Nov 4, 2026 •
Copy link
Copy Markdown
Member

Backports the 0.7.3 list onto release/0.7.x, one commit per item, in the list's order except where the table says otherwise. The list and porting rules are in https://github.com/gominimal/release-ops/issues/7.

This PR must land with Rebase and merge, so each commit reaches the line as is.

8c4bcd6d2 sets the version to 0.7.3-rc1. The commit after it, 38c080199, is a one-line e2e parser fix from #2741: the macOS lane failed without it. Every other subject is fix(...), or test(...) for a test-only item.

Commit Original PR Adaptation notes
fbe342a8e #2714 (T99) folded from #2724; line names; tokio::spawn; keeps the withdrawn-port test from #2732, without a dynamic hold
950f384aa #2718 (T109) folded from #2729; ports hunks in session.rs; no channel_method list or assertion
47df1d656 #2721 (T107) folded from #2735; no ProxyListening; serde_json_lenient in minvmd tests; server tests inline in server.rs
403d8e83d #2352 prerequisite for T100, split from #2738; setup.rs hunk in session.rs; renames carried here so it builds alone
e289e5fd2 #2725 (T100) split from #2738; finalize code in session.rs; registry() accessor only; tests adapted for no only_if_unfinalized
c7087f000 #2754 setup.rs hunk in session.rs; tokio::task::spawn_blocking
027dfe61c #2752 test-only; session/tests.rs; hunks for #2749 tests dropped here, since #2749 lands later on this branch
7981f0e2c #2332 prerequisite for T98, split from #2740; dormant-creation test edit dropped
947b955fd #2713 (T98) split from #2740; sessions.rs and inline rpc.rs tests; one ADDRESS_IN_USE_BODY
c36cd6e2a #2749 actor and manager hunks in session.rs and sessions.rs; ListenPlan gains hostnames and session_id; NET-129 dynamic-hold code, one test and hold assertions dropped; test ports use declared_port()
8acbf4d06 #2749 follow-up not a list item: fmt and clippy correction to the #2749 commit, kept separate because pushed history is not rewritten. No behaviour change
8746a3c7f #2716 (T106) folded from #2755; line names; BEP steering test edits and the NET spec hunk dropped; register_host_net keeps #2749's dial
7302e78d4 #2444 T102 prerequisite; the CLI policy view is in cmd/session.rs; line names
7299c15ed #2331 T102 prerequisite; line names in baseline.rs
55163d675 #2723 (T103) Order change: lands before T102, as on main, because T102 edits this commit's baseline and gate code. host_ip_box_with_vm_host and HostIpResidency live in minimal-client, since minimal-tui is its own crate here. The guard retires on every failed finalize. DropLimiter stays in egress_gate.rs
5dddc5e43 #2871 (T102) Behaviour change, for the release notes: host-address boxes reach only ports opened in [host-exposures]. Own-address boxes stay refused end to end. [#2956](#2956) stays out, so a project's [host-exposures] table opens nothing. Config lands in sessions/src/client/config.rs, without main's [secrets] sections
ec7524af0 #2720 (T104) box_actor/setup.rs lands in session.rs; tokio::spawn for crate::traced; mctx keeps the line's build_graph body; dropped session_id on the env channel and the dangling-hit test
b568f8dae #2234 sandbox hunk lands in sandbox2; the test names T104's fields
fd35986de #2296 sandbox:: reads sandbox2:: in mctx
cb85e78e6 #2846 none; Cargo.lock only. Clears the cargo-deny failure, RUSTSEC-2026-0433
82c8fe760 #2204 none
0ce56e4f3 #2777 (T110) policy.rs and create.rs hunks land in cmd/session.rs; RPC tests inline in lib.rs and rpc.rs; line names
93dd243a3 #2626 T111 prerequisite; the cmd/boxes/attach.rs change lands in cmd/session.rs
91d492a27 #2811 (T111) Reduced. cmd/boxes hunks land in cmd/session.rs. Dropped: min ls rows for a stopped VM's boxes, vm_running in its JSON and the (stopped) VM cell, because the line has no stopped status ([#2676](#2676)) and keeps box ids apart from session ids ([#2485](#2485)). Stopped VMs match by name only, never by id prefix. The ls_lists_boxes_on_stopped_vm test and a StopBox test arm go with them
14c0e8a8d #2303 Order change: Reliability follows T111, because T112 is left out. The test module gains the accept-all client handler main's connection tests already had
3d4e6ee18 #2242 none
169704442 #2319 none
94eb0ebe6 #2572 hand port; sandbox:: reads sandbox2::
e34798455 #2342 dash/ is the minimal-tui crate, so its own inventory test carries the gated rows
aeaf53cf9 #2338 none; follows [#2342](#2342)
2b8ead991 #2298 box_actor and box_manager hunks land in session/tests.rs and sessions.rs; crate::traced::spawn_detached becomes an instrumented tokio::spawn; line names
89a676d3e #2333 crate::traced::spawn reads tokio::spawn; line names
8c4bcd6d2 none: follows #2189 Sets package.version to 0.7.3-rc1 in Cargo.toml and the 8 workspace entries in Cargo.lock. No other changes

Reviewers: please check

  • #2749 landed reduced. The line has no NET-129 dynamic holds, so that code, one test and the hold assertions were dropped. Effect: a listen port's move onto a port another box holds only by a listener is not stopped up front. The bind fails and retries on each poll.
  • About 900 lines of egress_gate.rs range-probe admission came over with renames only. They arrived with #2749 and were not adapted further, so they need a read against the line.

Status

The list is ported. T112 #2832 is left out of 0.7.3: its reclaim needs the gate ledger from #2776. A proxy that starts on a substitute port stays there until the VM restarts.

#3004 has not merged on main, so it is not here.

This stages 0.7.3-rc1. The final release needs one more PR that sets package.version to 0.7.3.

Local verification

Run in the Lima VM through just, at 8c4bcd6d2: just fmt-check, just clippy, cargo check --workspace --all-targets --locked, just check-version (it accepts 0.7.3-rc1) and just deny pass. just test-installer (850 checks) passed at 89a676d3e, the commit before. just test runs 3929 tests: 3925 pass and 4 fail. Those 4 are known to fail only in the VM: a_hook_launch_does_not_advise_unenforced_placement and three finalize_install_undo_removes_* tests.

Each commit from #2444 on passed just fmt-check, just clippy and just test, with the same 4 failures, before it was pushed.

The VM lanes, the session e2e script, root integration and macOS were not run locally, so CI is their first run.

minimal-foundry Bot and others added 12 commits November 4, 2026 14:43
…#2714)

T99. The hostname proxy reads the target box's runtime-published ports (an allowed
expose, a published listener) from its live relay gate when the name resolves, so its
verdict admits exactly the ports a direct connection reaches. Admitted requests log
their route source at info, and refusals record route_source=none.

Backport of #2714, folded from #2724.

Adapted to the line:
- BoxGate, BoxPolicy and boxes::DynamicIngress become SessionGate,
  sessions::SessionPolicy and sessions::DynamicIngress.
- crate::traced::spawn becomes tokio::spawn in the new tests.
- The line's spawn_backend_on takes a full address, so
  proxy_forwards_runtime_published_port picks its port with a probe listener.
- The parity proptest checks the gate that carries the runtime publishes and asks
  admits_tcp, since admits_direct_tcp covers only the declared ports here.

Also keeps proxy_refuses_a_withdrawn_port_like_direct (NET-069, from #2732): it
publishes and withdraws through the gate only, because the line's listen watcher has no
record_dynamic_hold.

(cherry picked from commit 7097b96)
…2718)

T109. A port outside the box's --dynamic-range is refused with the range named, for
allow and ask boxes alike, and an ask box refuses it before any prompt. min net expose
--public is its own request and is refused because this host has no public surface. The
host-refusal and port prompt texts stop naming the VM host daemon, and the session e2e
script pins the new out-of-range text.

Backport of #2718, folded from #2729.

Adapted to the line:
- main's box_actor/ports.rs hunks land in session.rs.
- The line has no CHANNEL_METHODS list or channel_method helper, so the net-expose-public
  arm joins impl SessionChannel in env.rs without a list entry, and
  expose_public_refused_without_public_surface drops its channel_method assertion. The
  refusal text and log lines it pins are unchanged.
- boxes:: becomes sessions:: and BoxKeyPredicate becomes SessionKeyPredicate.

(cherry picked from commit 1bbd45c)
… the host (#2721)

T107. The in-VM daemon's vsock listener drops a peer at any CID but 2 at accept, before
reading a byte, and writes one rate-limited audited line naming the CID (NET-139). The
in-VM daemon reports each expose decision and each refused own-address egress flow over
the guest door, and the VM host daemon appends them to its audit log under per-box caps,
with a line counting suppressed records (NET-046). Expose decisions keep their own cap, a
withdrawn box flushes its window, guest-reported records carry reported_by=guest, and
box-admissions.log rotates to one kept generation at 1 MiB. The session e2e script expects
the new host audit records.

Backport of #2721, folded from #2735.

Adapted to the line:
- boxes::DynamicIngress becomes sessions::DynamicIngress.
- The line has no ProxyListening verb or reply, so ReportExposeDecision,
  ReportRefusedEgress and Audited stand without it.
- The listen watcher keeps tokio::spawn, since crate::traced is absent. The audit route
  is registered before the spawn and removed after the watcher's withdraw_all.
- minvmd has no serde_json here, so its host audit tests parse with serde_json_lenient.
- The vsock peer-CID tests join the inline tests in server.rs.

Dropped: ask_audit_line_spells_box_id_as_32_hex, which reached the cherry-pick as conflict
context from main's proxy work and is not part of #2721.

(cherry picked from commit abe99f7)
…aken (#2352)

Prerequisite for T100. A declared TCP host port that another process holds at the box's
published address fails the finalize before the Active write (NET-121), with an error that
names the port and the next step. The daemon probes on a native host. On a VM-backed host
the forwarder binds on the host OS, so the probe is skipped and the spawn's apply reports
the port. A forwarder's EADDRINUSE refusal fails the apply the same way.

Backport of #2352, folded from #2738.

Adapted to the line: main's box_actor/setup.rs hunk lands in session.rs, boxes::IpProto and
boxes::NetworkMode become sessions::, and the fake forwarder spawns with tokio::spawn. The
split from T100 carries those renames here, so this commit builds alone.

(cherry picked from commit 9437ba3)
…e holds (#2725)

T100. Finalize refuses a declared ingress host port that a program outside Minimal holds,
or that another box holds at the 127.0.0.1 fallback once a landed range verdict keeps the
box there. The typed PortInUseRefusal names the port and its holder, the daemon logs one
warn line, and min session activate ends with that refusal text alone. An unknown holder
is named as another program. On a VM-backed host, finalize binds the declared forwards
toward the handed switch address before the Active write, and the first attach takes the
host's "already bound" answer as the box's own forward. A box that finalizes at the
fallback under a pending verdict gets a watcher that reports an absent landing.

Backport of #2725, folded from #2738.

Adapted to the line:
- The finalize code lives in session.rs, not box_actor/setup.rs, and registry_name is the
  session module's own helper. In the CLI, activate_refusal applies to activate_session.
- boxes:: becomes sessions:: and switch::BoxGate becomes switch::SessionGate.
- The fallback watcher spawns with tokio::spawn under the same root span.
- OwnAddressReporter::registry comes without NET-129's dynamic-hold tracking. The attach
  path's holder lookup needs only the accessor.
- The line's DestroySessionRequest has no only_if_unfinalized, so the refusal tests assert
  the refused box is listed and not Active, destroy it plainly, and assert no record stays.

(cherry picked from commit 703bb7f)
…ot a holder (#2754)

A finalize at the shared 127.0.0.1 fallback probes each declared port with a
real bind. A parallel probe of the same port held it for the moment of its own
bind, so the other finalize refused the port as held by another program. The
probe now binds up to three times, 20 ms apart, and names a port held only
when every bind fails with EADDRINUSE. It runs on the blocking pool, and its
rule takes the bind as a closure so the test fails chosen binds in turn.

Adapted to the line: the box_actor/setup.rs hunk lands in session.rs, with
tokio::task::spawn_blocking for crate::traced::spawn_blocking.

(cherry picked from commit 8cac647)
Every own-address test box declared host port 18080. Since T100 a finalize probes a
declared port at the box's published address and refuses it when something else holds
it. nextest runs tests in parallel processes on one host, so tests that publish at
127.0.0.1 in lockstep can catch each other's probe and refuse each other's box.

Each test process now leases one port from 23000-23999, below the kernel's ephemeral
range, with a per-port lock file in a per-user directory, and every box in the test
declares it, so the collision tests still put two boxes on one port. Assertions read that
port back.

Adapted to the line: the change lands in session/tests.rs, and boxes:: reads sessions::.
Dropped: the hunks for #2749's tests, which the line does not have at this point, and for
main-only test text. #2749 carries its own tests with declared_port() when it lands below.

(cherry picked from commit 0be7561)
…he box range (#2332)

Prerequisite for T98. Allocation drew an address out of the reserved 127.0.64.0/24 without
measuring whether the host can bind it. Linux routes all of 127.0.0.0/8, but macOS answers
only the addresses lo0 carries, so on a host without the range aliases the zone answered
an address nothing listens at. The address book now hands a fresh box address only when
switch::loopback::probe reads the whole range present, and hands the 127.0.0.1 interim
otherwise. A probe refused for any reason but an absent address refuses the allocation.
A box keeps its address across the probe: an interim box resumes at the interim, and a
resume sends the address it was created at, so a restarted answerer hands it back.

Backport of #2332, folded from #2740.

Adapted to the line: the answerer imports sessions::core::zone_answer. Dropped: the edit to
main's dormant-creation tests, since the line has neither those tests nor dormant rows.

(cherry picked from commit 379c2f0)
…olves to (#2713)

T98. A microVM daemon walked the host's loopback once, at its start, so after min
finalize-install new boxes stood at the 127.0.0.1 interim until the VM restarted. The
create handler on a VM-backed daemon now asks the forwarder's walk, not the guest's own
lo, and every session start walks again, so a present landing moves boxes at the interim
onto their range addresses and a present-to-absent walk moves them back. Walks run one at
a time on the shared probe port. A held probe port counts as bound, a leftover probe
forward is released before it is read, and a walk that gets no answer keeps a present
verdict. The absent landing also moves the node's own address to the interim. The NET-040
spec line and the macOS e2e case come with the change.

Backport of #2713, folded from #2740.

Adapted to the line:
- box_manager.rs and box_manager/handle.rs hunks land in sessions.rs: RangeWalk,
  RangeWalkSource and RangeWalkAnswer, the range_walk cell on ManagerHandle and
  WeakManagerHandle, session_start_range_walk and install_range_walk_standin.
- rpc/tests.rs hunks land in the inline tests in rpc.rs.
- crate::traced::spawn becomes tokio::spawn in the stand-in forwarders.
- ADDRESS_IN_USE_BODY is shared with #2352: one copy stays in net/policy.rs.

(cherry picked from commit 64d3358)
…ge lands present (#2749)

Fixes a regression from T98. When a box's range probe lands present, its declared forwards,
its min net expose forwards and its listen-published forwards move from the 127.0.0.1
interim to the range address, so the name and the forward answer at one address. The host
egress gate admits the bounded range probe, the walk is contention-safe, and VM host-net
boxes dial their listeners at the guest's 127.0.0.1.

Backport of #2749.

Adapted to the line:
- box_actor.rs and box_actor/{handle,ports}.rs hunks land in session.rs, box_manager.rs and
  box_manager/handle.rs hunks in sessions.rs, and box_actor/tests.rs in session/tests.rs.
  The line has no box_actor/kind.rs, so its message-name hunk has no target.
- crate::traced becomes tokio::spawn, and box names become session names throughout.
  egress_gate.rs gains the tracing::Instrument import that main already had.
- ANONYMOUS_SESSION becomes pub(crate) for the actor's move log lines.
- The tests' port 18080 reads declared_port(), matching #2752, which landed earlier here.
- ListenPlan gains the hostnames and session_id fields the move reads. On main they came
  with NET-129's dynamic holds, which the line lacks; only the two fields come here.

Dropped because the line has no NET-129 dynamic holds:
- ListenPlan's record, release and move hold helpers, their calls in the watcher, and the
  bound_address helpers that fed them. A listen forward's move needs none of them: a port
  another box holds by declaration still stops the move, read through shared_port_holder.
- The test a_listen_port_whose_move_failed_releases_its_hold_where_it_was_bound.
- The dynamic-hold assertions in the runtime-publications landing test and in
  a_listen_port_does_not_move_onto_a_port_another_box_holds.
- The host_net.insert in register_host_net: that field arrives with T106, next.

(cherry picked from commit aa6964d)
…2749)

Follow-up to the #2749 backport. That commit reached the branch before the group's fmt and
clippy gates ran, and pushed history is not rewritten, so the correction is its own commit.

ListenPlan::new now takes nine arguments, so it carries main's too_many_arguments expect,
which came with NET-129 there. move_exposed_ports fits one line at the line's shorter
indent. No behaviour changes.
…e proxy callers (#2716)

T106. NET-141: the relay's DNS gate answers REFUSED, before any onward query, for every
name outside the box zone that a declared allow_dns_hosts list does not match. It did this
only for a deny-all box, so a box with a partial list still sent the name off the machine.
An empty list that admits subnets keeps forwarding. Each refusal writes one rate-limited
warning, keyed by a bounded hash bucket of the name.

NET-070: the hostname proxy names the box behind a connection from the node's address
through the socket's inode, its holder and that process's classifier leaf, and applies that
box's egress rules. While a host-address box is live, a connection whose owner cannot be
named is refused. Owner lookups are capped at four at once, and a wait over five seconds
is refused as unattributed. session-e2e.sh asserts the partial allow list refuses fast.

Backport of #2716, folded from #2755.

Adapted to the line:
- Box actor and box runtime hunks land in session.rs and session_host.rs.
- boxes, sandbox and box_runtime read sessions, sandbox2 and session_host, with
  SessionPolicy and SessionKeyPredicate. crate::traced becomes tokio::spawn and
  tokio::task::spawn_blocking, and the line's policy has no quic443 field.
- register_host_net keeps #2749's dial at the guest's 127.0.0.1 beside the host_net record.

Dropped: the edi
8000
ts to the BEP steering tests in dns_gate.rs, since the line has no BEP
steering, and the NET spec hunk, since the line's NET-141 text differs from main's.

(cherry picked from commit 302edaf)
@coderabbitai
coderabbitai Bot commented Nov 4, 2026 •
Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: a4b54e96-606d-4268-b97e-63917eb35d3c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

gominimal-aw-bot Bot and others added 11 commits November 4, 2026 15:29
…tch ports (#2444)

Prerequisite for T102. The node-plane baseline's compiled set admits by subnet alone, so
once the baseline phase goes in force the gate's node-plane arm would admit frames to the
switch's host alias at every port. The in-force arm now opens the alias only to TCP at the
daemon's fetch port and refuses every other port and protocol as the infrastructure drop.
The announced arm and every box row's verdict are untouched. The policy text and JSON views
spell the alias endpoint with its bound, as 100.64.255.254/32 tcp/443.

Backport of #2444.

Adapted to the line: the CLI's policy view lives in cmd/session.rs, not cmd/boxes/policy.rs,
and boxes:: reads sessions::.

(cherry picked from commit 6471908)
Prerequisite for T102. The announced node row carries the frames of every box sharing the
guest root namespace, so its reach through the host alias was admitted at every port. The
alias now opens only at the host exposures, the inclusive port ranges listed in
MINVMD_HOST_EXPOSURE_PORTS, read once at VM start. Unset means no exposure, and every other
port is the infrastructure drop for every row. An exposure matches only a TCP or UDP frame,
so an ICMP frame never opens the alias. The parser refuses port 0 and names a bad term. The
VM lanes export the port their NET-003 check reaches, and add the refused-port half.

T102, next, replaces this variable with the [host-exposures] table.

Backport of #2331.

Adapted to the line: boxes:: reads sessions:: in baseline.rs.

(cherry picked from commit 0ce1fd8)
…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)
…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)
…dict (#2720)

T104, NET-155. A box's package builds run in the box's classifier leaf, or for an
own-address box on a task network of the box's own, so the box's egress verdict decides
their connections. That covers build and materialize side operations, launch builds,
in-box installs, patched builds and task runs. A box whose launch placed it in a leaf
refuses a build, task or check when the leaf is gone, so a build never runs outside the
verdict. A build the box's rules refuse names the destination and the box, and each build
start logs the box, package and leaf. A box with no leaf and no own address builds in the
daemon's cgroup as node-plane traffic, as before, and logs that it does.

Backport of #2720.

Adapted to the line:
- box_actor/setup.rs hunks land in session.rs, box_ops.rs is session_sop.rs, and the tests
  land in session/tests.rs, with super::setup:: read as super::, a Record literal of the
  line's fields, and local rewrite_record and read_record helpers.
- boxes, sandbox and box_manager read sessions, sandbox2 and sessions, and crate::traced
  becomes tokio::spawn and tokio::task::spawn_blocking.
- mctx keeps the line's build_graph body, without main's dangling-hit replan, and only
  gains the local_orchestrator call that carries the leaf.

Dropped: main's session_id on the env channel, which T104 only carried as context, the
dangling-hit test in op/src/patched.rs, and the test for #2234 in op/src/specs.rs, which
comes with #2234 below.

(cherry picked from commit a26f89b)
)

A non-extracted Source resolves to the download cache's own file, and it was hardlinked
into the build's writable /build, so a build that appended to its input changed the cached
source. It is now mapped as FileCopy, as Local files already are. FileCopy no longer
truncates an existing destination: copy_input_file creates with create_new, and on a
collision it keeps the first file and warns, as the hardlinked File input does.

Backport of #2234.

Adapted to the line: the sandbox hunk lands in sandbox2, and the new test's SpecBuild names
T104's classifier_leaf and box_network fields.

(cherry picked from commit 13aaa43)
Task argument values were spliced into command strings unquoted, so in a single exec string
a value with spaces shattered into several argv entries, and in a bash script a value could
end the quoted region and run as shell code. A single exec string is now split into argv
words with each string value standing in as an opaque token, then each value fills its own
word, and no shell runs it. A bash script gets ${MIN_ARG_<NAME>} for each string reference,
with the value bound in the invocation's environment. The daemon and mip launch paths pass
Invocation::envs to the sandbox command. An enum value keeps its real value, since it is
validated against the author's permitted set.

Backport of #2296. Adapted to the line: sandbox:: reads sandbox2:: in mctx.

(cherry picked from commit cfcff04)
Backport of #2846. Cargo.lock only; the advisory is the one failure in the 0.7.x cargo-deny job.

(cherry picked from commit 1aa79ab)
.cargo/config.toml held one thing: a [registries.buf] entry whose token line was a bearer
credential in plain text. No dependency resolves through that registry, no crate names
registry = "buf", and no workflow or script reads it, so the whole file goes rather than the
one line. The token is still in history and needs rotating at buf.build.

Backport of #2204. Applied unchanged; the line's file matched main's.

(cherry picked from commit 813e4e5)
…2777)

The ListSessions reply gains a map keyed by box id with the host loopback address the box's
ports answer at, its effective egress posture, and whether it stands on the 127.0.0.1 fallback.
All three are serde-defaulted, so an older daemon reads as unreported. min ls gains an ADDRESS
column and --json carries the same facts. The EGRESS column prints the posture in the words min
session policy uses, from one shared function, and the activate host line and the BOX NAMES row
state how a box name opens through one resolver function. A VM node's host-address box reports
no address until the node is granted one, since it is never mirrored onto 127.0.0.1.

Backport of #2777. Adapted to the line: the policy.rs and create.rs hunks land in
cmd/session.rs, and the minimald-rpc and minimald rpc tests land inline in lib.rs and rpc.rs,
with the line's names (SessionStatus, EffectiveSessionPolicy, a built CreateSessionRequest).

(cherry picked from commit 08045bf)
… net forward (#2626)

NET-058 says a box name alone decides which VM a verb looks in, and attach honours it: the
selected VM's daemon first, then every VM's socket. min session exec, min session run and min
net forward still looked only in the selected VM, so a box activated with --vm was listed by
min ls and reachable by attach, and exec answered "No session found matching". The three now go
through the resolution attach uses, generalised by the verb that leads its one announcing line,
and the hand-off runs over the owning VM's socket. NET-058 names the verbs it now covers.

Backport of #2626. Adapted to the line: the cmd/boxes/attach.rs change lands in cmd/session.rs,
with the line's session_announce names.

(cherry picked from commit f2e6ed6)
@agent-137 agent-137 left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review of b568f8d, triaged for a backport. I checked each finding below against this head and against main. All of them are on main too, so I do not suggest changing this branch's commits. Fix on main, then cherry-pick per release-ops#7.

Worth deciding before 0.7.3 ships (NET-155 enforcement)

  1. An in-box min patched-pkg runs outside the box's egress leaf. crates/mctx/src/env.rs:268-271 (EnvChannel::run_patched_pkg) passes classifier_leaf: None, box_network: None. The comment says "a client-side patched build runs for no box", but a daemon task exec builds its env through this channel: minimald/src/exec.rs:483 → make_env_with_network → EnvChannel at mctx/src/env.rs:1172. That task context does carry the leaf (exec.rs:414 refuses without it), and cloned_reinit copies it into build_ctx. So in a deny-all task box, a patched build's downloads run in the daemon's cgroup and are not refused. min install / min run in the same box are refused. The daemon's own patched path (minimald/src/env.rs:1388) already passes the leaf. Fix: classifier_leaf: build_ctx.classifier_leaf().cloned(), plus box_network the way minimald/src/env.rs:1395 picks it. Same code on main.
  2. A failed host_ip admit is silent. HostIpResidency::admit returns None on a failed or unanswered exchange. That is box_registration.rs:822 here and cmd/boxes/vm_host.rs on main. The activation then succeeds with only a tracing::warn. On a current VM host that timed out, the gate drops every frame the box sends (HostIpUnbounded), and nothing the user sees says why. An older daemon is fine, because its gate does not enforce. This is not a code bug, but a box with no network and no explanation is a support case. At least a line on the start, or a min ls/min box show state, would help. Same on main.

Follow-up on main, not this backport

  • Rule union across resident host_ip boxes (minvmd/src/box_registry.rs host_ip_verdict, and the shared cohort pin record). A deny-all host_ip box can reach a name that another resident box allows and resolved. The admit's log line says the union is intentional, so this is a design question, not a porting bug. I will file it as an issue on main.
  • The attribution semaphore (minimald/src/net/proxy.rs:356): 4 slots with a 5 s wait, then a 403 "cannot tell which box opened this connection". Under parallel host-side connections, that refuses traffic NET-071 lets through. Check whether it is new in this backport.
  • The NET-155 refusal line rarely prints (minimald/src/env.rs:2435). patched_build_refusal needs the build's error string to contain the refused host, but that string is usually only "exit status N".

Notes only

  • host_ip_box_at (attach.rs:568) takes any HostIpBox { .. } reply as success. Today admit_host_ip_box always answers resident: true with the request's id, so this cannot trigger now. Checking resident && session_id == id is cheap hardening.
  • HostIpResidency::drop (box_registration.rs:851) does a blocking control exchange on a tokio worker. That happens only on a failure path.
  • Destroy sends RetireHostIpBox for boxes that never admitted (cmd/session.rs:245). Gating it on HostNet saves a round-trip when the VM host is down.
  • task_producer reads the box record twice (exec.rs:455).

Checked and dropped: an own-address box always gets its build network (own_address_build_network returns Some for every OwnIp box, built lazily), so task_build_refusal not covering it is fine.

minimal-foundry Bot and others added 4 commits November 4, 2026 16:07
Policy and destroy resolve a box's VM from its name, as attach, exec, run and net forward do.
A box no running VM holds names --vm, a box on a stopped VM names the command that starts it,
and a name any two VMs hold, running or stopped, is refused with every VM listed. A relay
failure writes the unwind before the attach error prints, so the error stays on screen.

Backport of #2811. Adapted to the line: cmd/boxes/attach.rs and policy.rs hunks land in
cmd/session.rs. Dropped, because the line has no stopped session status (#2676) and keeps box
ids apart from session ids (#2485): min ls rows for a stopped VM's boxes, vm_running in its
JSON, and id-prefix matching against a stopped VM's records, which match by name only here.

(cherry picked from commit 2e3962f)
…ine (#2303)

russh bounds every session-loop transport write only by the inactivity timer, and minimald set
it to None, so a write that never completed parked the session loop forever. Keepalives are
dispatched from that same loop, so they stopped firing too: an attach whose transport wedged
went dark in both directions and survived every keepalive budget. The timer is now armed one
interval past the keepalive budget, so a healthy idle attach is never reaped and a wedged one is
torn down.

Backport of #2303. Adapted to the line: the test module gains the small accept-all client
handler main's connection tests already had.

(cherry picked from commit 02f3af9)
…rename (#2242)

FileCache::write_through created the content-addressed file in place and streamed into it. A
stream error left partial bytes under the pinned hash, and a concurrent fetcher could read the
truncated file while the download was in flight. It now streams into a uniquely named temp file
in the same sha directory, fsyncs it, verifies the SHA-256, and only then renames it onto the
final path. The temp file goes on every error path, and a write error returns an I/O error
instead of panicking. A failed fsync of the directory warns and does not fail the download.

Backport of #2242. Applied without adaptation.

(cherry picked from commit 40420c9)
…2319)

The post-receive hook checked out every pushed branch, so pushing a branch other than the
workspace's current one moved HEAD and swapped the worktree to it. A push to a non-checked-out
branch now only moves the ref. A first push into a fresh workspace checks out the first branch
and leaves the rest as refs, and a push onto a detached HEAD says so on a remote: line with the
git switch that re-attaches it. The cli-min push paragraph describes all three.

Backport of #2319. Applied without adaptation.

(cherry picked from commit 0a297d0)
bryan-minimal and others added 5 commits November 4, 2026 16:12
…#2572)

hakoniwa arms PR_SET_PDEATHSIG(SIGKILL) in every container it forks, and Linux delivers that
signal when the forking thread exits. The task-run path in exec.rs forked its container on
whichever tokio worker ran the exec. Any block_in_place on that worker displaces it, tokio
retires it after its 10 s keep-alive, and every idle task session forked on it dies with exit
125. The spawn now goes through on_fork_thread, as the session launch and run_with_cancel do.

Backport of #2572. Adapted to the line: sandbox:: reads sandbox2::.

(cherry picked from commit cafa674)
…2342)

An uninstall deleted the binaries but left a running daemon in place, so a reinstall autospawned
nothing and every daemon-backed command then hit the old daemon across a version skew. The
uninstall walk now stops the daemon first, while the min that matches it is still on disk, with
the same escalation as an upgrade. An install that starts with no min on disk compares the
installed min's version with the answering daemon's and force-stops a mismatched one.

The dashboard's discovery connections are now gated as min ls is, so it refuses a skewed daemon
before the TUI takes the terminal, and a provider that reappears skewed joins only under the
override. The skew message's recovery line names min stop --force.

Backport of #2342. Adapted to the line: dash/ is the minimal-tui crate, so its inventory test
carries the gated rows, and the rows in minimal's connection inventory drop out.

(cherry picked from commit 2e67f8a)
…2338)

A client attached during an upgrade autospawns a replacement daemon the moment the pre-swap stop
ends, and that daemon serves the old version from the binaries the run is about to replace. The
installer now stops once more after every swap, only a daemon whose version differs from the
installed min, and --force only with consent. A pre-swap yes covers only the sessions it listed:
sessions opened after it are asked about again, and with no terminal the daemon stays running
and the installer names min stop --force. An EXIT trap runs the same stop when the run dies
after the first executable swap. Spec 07 R5.5 describes it.

Backport of #2338. Applied without adaptation, after #2342 as on main.

(cherry picked from commit e6cbd46)
…start (#2298)

A minvmd load reinstated each standing row detached, arming the 45 s detach grace. A VM's cold
boot outlasts that, so the row, and with it the box's published name and declared ingress, was
gone before the guest daemon started. Reloaded rows now wait under the resume bound. The guest
resume also started a restored box's actor but never its host, so its shuttle never attached and
ingress stayed unbound until a client's first exec or attach. Resume now launches an own-address
box's host detached from the resume, and the attach announces the box's lease, so an idle box
carries its row.

Backport of #2298. Adapted to the line: box_actor and box_manager hunks land in session/tests.rs
and sessions.rs, crate::traced::spawn_detached becomes an instrumented tokio::spawn, and line
names replace BoxGate, BoxKeyPredicate and boxes::. #2333 below moves the announcement.

(cherry picked from commit 10d34a5)
A gated attach wrote its framed gratuitous ARP into the /connect handshake, before the relay
owned the stream. gvproxy's hijack discards bytes already buffered, so the ARP's tail was read
as a bogus length prefix and box ingress died. The handshake now speaks only the upgrade, and the
relay sends the announcement as its first frame, one short delay after it starts. Only the vsock
attach announces: on the local-socket attach the tap wears a kernel-chosen MAC, so the
announcement would point the gateway at a MAC the box does not wear, and there is no host-side
row for it to carry.

Backport of #2333. Required follow-up to #2298. Adapted to the line: crate::traced::spawn reads
tokio::spawn, and line names replace BoxGate and boxes::.

(cherry picked from commit 1bec117)
@norrietaylor
norrietaylor marked this pull request as ready for review November 4, 2026 23:38
@norrietaylor
norrietaylor requested a review from a team as a code owner November 4, 2026 23:38
@minimal-foundry
minimal-foundry Bot commented Nov 4, 2026 •
Copy link
Copy Markdown
Contributor

Reviewed: 2 findings, none blocking at 38c080199d75.

Address the comments on the diff.

Clobber: was not checked in depth. If that was not intentional, restore the earlier change.

@minimal-foundry minimal-foundry Bot 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.

minimal-review — approved

Verdict APPROVE — No findings.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

@minimal-foundry minimal-foundry Bot added the review:approved minimal-review approved the current head label Nov 4, 2026
The release run stages the version that package.version declares, and a
staged version is permanent. A candidate number keeps 0.7.3 free for the
final build.
@minimal-foundry minimal-foundry Bot 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.

minimal-review — advisory review

Verdict COMMENT — 2 findings, 2 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread Cargo.toml Resolved
Comment thread .cargo/config.toml Resolved
@minimal-foundry minimal-foundry Bot removed the review:approved minimal-review approved the current head label Nov 4, 2026
@norrietaylor
norrietaylor enabled auto-merge (rebase) November 4, 2026 23:50
A macOS VM host prints "this VM's host daemon answers its box names on
127.0.0.1:<port> (UDP)", so the parser in
fresh_install_range_lands_on_macos found no port and failed the lane.

This takes the one line from #2741 (de32bd3). The rest of that commit
stays off the 0.7 line. The case now matches main.
@minimal-foundry minimal-foundry Bot 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.

minimal-review — advisory review

Verdict COMMENT — 2 findings, 2 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread .cargo/config.toml
Comment on lines -1 to -4
[registries.buf]
index = "sparse+https://buf.build/gen/cargo/"
credential-provider = "cargo:token"
token = "Bearer c5ceab0fe4fbdddabe0aefb73a1351aa7f77149a9df3837af3f27e4f4171119f"
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.

MEDIUM · security · confidence 0.55

This PR deletes a committed buf registry bearer token. The token stays in the history of release/0.7.x and in every published 0.7.x tag. Deleting the file does not revoke it. Confirm that the token was rotated when #2204 landed on main. If it was not, revoke it now.

What the reviewer checked

-token = "Bearer c5ceab0f..." in .cargo/config.toml

Hand this to an agent
Act on a code-review finding.

File:       .cargo/config.toml
Lines:      1-4  (in the pre-change version of the file)
Category:   security
Severity:   medium (reviewer confidence 0.55)

Finding:
This PR deletes a committed buf registry bearer token. The token stays in the history of release/0.7.x and in every published 0.7.x tag. Deleting the file does not revoke it. Confirm that the token was rotated when #2204 landed on main. If it was not, revoke it now.

First verify this against the code yourself. It is model output and
may be wrong. If it is wrong, or the tradeoff was deliberate, say so
and change nothing.

👍 useful · 👎 noise — react to tell the reviewer. Replying works too, and says more.

Comment on lines +2758 to +2766
{
Ok(f) => f,
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => return Ok(false),
Err(e) => return Err(e),
};
let mut input = fs::File::open(src)?;
std::io::copy(&mut input, &mut out)?;
out.set_permissions(input.metadata()?.permissions())?;
Ok(true)
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.

LOW · correctness · confidence 0.50 · in copy_input_file

copy_input_file creates dest with create_new before it opens src. If the src open or the copy fails, an empty or partial dest stays in the sandbox dir. The caller returns an error, so the sandbox is dropped. A retry that reuses the dir would see AlreadyExists and skip the copy. Open src first.

What the reviewer checked

let mut out = ...create_new(true).open(dest)...; let mut input = fs::File::open(src)?;

Hand this to an agent
Act on a code-review finding.

File:       crates/sandbox2/src/lib.rs
Lines:      2758-2766
Category:   correctness
Severity:   low (reviewer confidence 0.50)

Finding:
`copy_input_file` creates `dest` with `create_new` before it opens `src`. If the `src` open or the copy fails, an empty or partial `dest` stays in the sandbox dir. The caller returns an error, so the sandbox is dropped. A retry that reuses the dir would see `AlreadyExists` and skip the copy. Open `src` first.

First verify this against the code yourself. It is model output and
may be wrong. If it is wrong, or the tradeoff was deliberate, say so
and change nothing.

👍 useful · 👎 noise — react to tell the reviewer. Replying works too, and says more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

No 49AF ne yet

Development

Successfully merging this pull request may close these issues.

4 participants

0