Repository navigation
Tags: gominimal/minimal
Tags
feat(minimal): keep store rules under min --config-dir in step with b… …ep serve (T92) (#2666) * feat(minimal): refuse a store handle under a rule bep serve does not read A store rule read from a min --config-dir override reaches the handle only when bep serve, which reads the default config dir, holds the same rule. Otherwise min refuses the mint and names both locations (BEP-032). * docs(bep): name the min refusal in place of the config-dir issue link * chore(minimal): allow the store mint's eight arguments in clippy * docs(minimal): name the two copies of the store rule conversion proxy_rule in min copies store_rule in the bep binary. Each doc comment now names the other copy, so a change to one reaches both. * refactor(bep): share the store rule conversion with min's mint check Move store_rule into the bep library as StoreRule::from_config. bep serve and min's mint check now decide inject with one function, so the copy in mint.rs and its notes are gone. Give each cause of a store handle refusal under --config-dir its own text: an unread file, a refused rule, a deny rule and a rule that differs. Each names the file that bep serve reads. --------- Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
feat(bep): inject store values in bep serve (T67) (#2597) BoxRedeemer now overrides redeem_reference. An admitted store member is given to the forwarder as Redeemed::Store, with the store, identifier and injection form, so the forwarder reads the value per request. A store member is judged against the handle's upstream and the current rule's authorities. Any other value outside Authorization is still refused as sealed_header_misplaced before it is decided. Each flow bep serve builds now reads from the host store. The redeemer takes its [secret-store-rules] through with_rules. bep serve passes none until T25 reads them. Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
docs(design): split-user VMM provider directory and socket ownership … …(T57) (#2546) * docs(minvmd): design provider dir ownership for a split-user VMM Names the creating user, the mode and the opening users of every socket and file in the provider directory when gvproxy and the VMM child run as a dedicated user. A test in sock.rs checks the table against the socket names the code uses. The architecture ruling is still pending. * docs(minvmd): add sticky bit and entry checks to split-user design A 2770 provider directory lets minimal-vmm unlink control.sock and bind its own socket at the path. The model now uses 3770, and it says how the supervisor, bep serve and min check each entry before they bind, chmod or connect. The design test now also checks the gvproxy.yaml, switch, bep and control socket rows, and the fallback table. * docs(minvmd): give the VM user its own dir in the split-user design Apply the arch ruling: the provider dir is 0710 group minimal-vmm and the VM user cannot write it. Everything minimal-vmm creates moves to vmm/. State the peer uids each door admits, the sudo -n NOPASSWD spawn and its refusal, and the one-member group. The test now checks each row's creator. * docs(minvmd): keep the gvproxy switch socket away from the VM user Address round 3 of the split-user design review. - Arch C1: the VMM child sets the mode of boot.log (0640) and data-vol.raw (0600) itself, and the supervisor refuses group or other write bits. Fact 4 says umask 0007 gives 0660 to a file with no mode. - gvproxy stays with the login user, so its 0600 switch socket is reachable only through the egress gate. A new section lists what a compromised VMM can and cannot reach. The fallback says it does not protect the gate. - Status is "proposed, arch conditions open". - The sudoers drop-in names root-owned absolute paths, fixed arguments and env_reset with a named allow-list. Fact 8 follows. - The sock.rs test comment promises only what its hand-written list proves, and cites the review thread, not a ruling. * docs(minvmd): run gvproxy as a third user in the split-user design Apply the arch ruling at e8be045. gvproxy runs as minimal-gvproxy, apart from <login> and minimal-vmm, so it shares no writable state with bep serve. - The switch socket moves to a root-created gvproxy dir (0750, minimal-gvproxy), bound under spawn umask 0007 as 0660 group minimal-gvproxy. - gvproxy.yaml moves to a gvproxy config dir that <login> owns (0750, group minimal-gvproxy). The file is 0640, and minimal-vmm cannot read it. - The sudoers rule for gvproxy sets umask=0007 with umask_override. - min finalize-install moves data-vol.raw with openat, O_NOFOLLOW, nlink and inode checks, and fchown on descriptors, never by path. - The open item on the switch socket is closed by the ruling. - The test checks the gvproxy dir, the switch socket and gvproxy.yaml rows. * docs(minvmd): open the egress gate placement under Gatehouse 6.10 Quote Gatehouse section 6.10 line 831 as written: "the BEP role and the EGW/gvproxy role". Add a section on what a compromised supervisor can reach. The egress gate and switch::bep_host parse guest frames as the login user, so the model alone does not meet section 6.10. Add the open item for arch to rule on their placement, and set the status to "proposed, arch conditions open". check_table now refuses a row whose mode has no group or other bits but names an opener other than the owner. * docs(minvmd): run the egress gate in an EGW process as minimal-gvproxy Write in the arch ruling at 4308298 on Gatehouse section 6.10 line 831. The egress gate and switch::bep_host move out of the supervisor into an EGW process that runs as minimal-gvproxy. bep serve stays the login user. The EGW process creates gvproxy-gate.sock in a new setgid gate dir that minimal-gvproxy owns with group minimal-vmm. minimal-gvproxy is never in the minimal-vmm group, so it gets no access to vmm/. gvproxy-bep.sock moves to the gvproxy config dir, where minimal-gvproxy can search and minimal-vmm cannot. listener.rs admits the minimal-gvproxy uid and keeps the token check. The switch socket becomes 0600. Replace the compromised-supervisor section with one for the EGW process, remove the open placement item, and set the status to name the ruling. The table test now expects the gate socket from minimal-gvproxy and the proxy socket in the gvproxy config dir. * docs(minvmd): model the supervisor channel to the EGW process Name the socketpair that carries the box table, the withdrawals, the attachments and the token from the supervisor to the EGW process, with its creating user, opening users, direction and peer check, and add it to the ownership table. The EGW process drops every frame and refuses every publish request until it holds the first complete table, and exits when the channel closes. The supervisor handles that exit on the gvproxy exit path. Pass every per-run value on descriptor 0, so each sudoers rule names only literal arguments for one VM. Link the arch review comments from the status. Add the gate and BEP socket rows to the fallback table and to the test, and drop the review history from the test comments. * docs(minvmd): bound the EGW withdrawal ack and cite networking 4.3 The supervisor waits at most 5 seconds for a withdrawal acknowledgement. On timeout it stops the EGW process and gvproxy on the EGW exit path, then answers the withdrawal. "What T38 changes" adds the test for that path. Cite networking design section 4.3 lines 83 and 85 for default deny and fail closed, in place of Gatehouse. Rename the design test to say that it checks a fixed list. The new name keeps the plan's test filter as a prefix. * docs(minvmd): stop the EGW process as minimal-gvproxy before the ack A SIGKILL from the supervisor reaches sudo, not the EGW process behind it. Add a stop rule to the drop-in that sends SIGKILL to the process group of the spawn as its own user, after a SIGTERM that sudo relays. The supervisor answers a withdrawal as done only after it observes the exit, and names the fail-closed result when no exit comes within the bound (networking 4.3). The T38 stub EGW process now also ignores SIGTERM. * docs(minvmd): shut down the write side of the EGW channel on stop Step 1 of the stop sequence now calls shutdown(2) with SHUT_WR on the supervisor end and keeps it open, so the supervisor can read end of file there after the EGW exit. The supervisor closes its end only after the observed exit or the fail-closed result. The first stop test in "What T38 changes" now pins the SHUT_WR step. --------- Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
fix(minimald): take an exec's input off the SSH channel as it arrives… …, not as the command reads it (#2197) russh credits the client's send window on receipt and gives the server no way to hold it back, so a command that stops reading its stdin parked the whole SSH connection, every channel included (inbox#1089). The pump now takes input off the channel as it arrives into a held queue the bridge reads from, bounded per exec (256 MiB) and for the daemon (512 MiB): past a bound a command that reads nothing for 10 s is ended with a notice on its stderr, a command that has not started is waited for up to 300 s, and an exec holding nothing at the daemon bound waits for the others up to 60 s. A single program that closed its stdin is not charged for input it will never read, which needs the injection shim to let go of its own copy of that stdin once the command is spawned. An exec whose end comes while its command is starting ends at once, and the command a late start yields is reaped. Ported onto main across the destroy-notice work (#2351): the destroy wrapper takes an optional token so the bridge hands back one stream type for a late start, and bridge_noting_reason names the three ways an exec ends.
chore(release): merge tag v0.7.1 back into main (#2474) * fix(promote): find the draft release by listing and publish it under its tag (#2220) The publish job looked the release up with `gh release view v<semver>`. A draft has no tag yet, so the lookup answered 404 for exactly the release the job exists to publish, and the 0.7.0 promotion failed there. Publishing a draft without naming tag_name also tags it with the draft's untagged-<hex> placeholder rather than v<semver>. The job now finds the release by listing releases and matching tag_name, refuses when more than one carries the tag, and publishes by id with tag_name and target_commitish set. * fix(deps): bump rustls-webpki and webpki-root-certs past the emSign advisories (#2466) RUSTSEC-2026-0382 (rustls-webpki 0.103.15) and RUSTSEC-2026-0383 (webpki-root-certs 1.0.9), both published 2026-10-20, red cargo-deny on every PR. rustls-webpki 0.103.16 rejects paths through the distrusted emSign intermediates, and webpki-root-certs 1.0.10 drops the emSign ECC Root CA - G3. Lockfile-only change. (cherry picked from commit c0ac8c5) * chore(release): bump version to 0.7.1 Hotfix release carrying the rustls-webpki and webpki-root-certs bumps from #2466 (RUSTSEC-2026-0382, RUSTSEC-2026-0383) onto 0.7.0. Claude-Session: https://claude.ai/code/session_01FnY4WzhrnmSEByMntmqsVF
feat(bep): verify store handles against the client's registered key a… …nd the current rule (#2467) * feat(bep): verify store handles against client keys and the current rule Add the store-handle wire format (an ES256 compact JWS), the registry of client keys, and the BEP-064 check. The proxy reads the rule for each request, so an edited [secret-store-rules] rule decides the next request. A sign-in reference has the GitHub module as its rule. The redemption decision makes the handle check after the revocation check, and refuses a bad handle as store_handle_invalid. Add a Kani harness for it and raise the bep harness count in scripts/kani.sh. * fix(bep): bind the store handle to the opened member Add handle::read_opened, which reads the handle from the sealed member's own jws. A handle of another kind than the member, or a value opened for another module, has no rule and is refused as store_handle_invalid. A sign-in reference whose store is not the module's id now has no rule. The debug line logs the unverified store and id with Debug escaping. * fix(bep): bind store handles to the box and the Box Host Follow the Gatehouse store-handle wire format: typ gatehouse-store-handle+jws, and sub, node_id, iat and jti in the payload. The sealed member gives the kind, not the payload. A handle whose sub or node_id differs from the opened value refuses as context_mismatch. read is private, so only read_opened builds a handle, from the sealed member. * docs(bep): carry the handle's box and host binding into spec 24 BEP-063 names the handle's typ and the sub, node_id, iat and jti claims. BEP-064 and its property refuse a handle whose sub or node_id differs from the sealed value's box or host, as Gatehouse §6.10 does. * fix(bep): log an unparsed handle upstream apart from the signature A handle that verifies but names an upstream that does not parse is still refused, but its debug line now says unparsed_upstream, not signature. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J3Guwr3mQPqSeGMcjzVjTd * fix(bep): bound a redeemed authority by the handle's upstream decide now refuses an authority outside the handle's own upstream as outside_bound_set, so a bound set wider than the checked handle cannot widen where the value goes. Adds a test for the mismatch. --------- Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
feat(bep): refuse off-set authorities, off-port and expired values (#… …2448) decide now refuses a value whose clock is at or after its expiry, or whose box a revocation in force names (BEP-024). It refuses a connection to a credentialed hostname on an undeclared port (BEP-031), and an authority outside the bound or the current host set (BEP-021). Each refusal names its check as an audit marker: expired, revoked, off_port, outside_bound_set or outside_current_set. Add three Kani harnesses and expect six in scripts/kani.sh. Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
refactor(minvmd): move canonical_box_name into box_registry (#2344) box_registry imported net::answerer::canonical_box_name while answerer imported box_registry's types, so the two modules imported each other. The helper belongs with the registry that holds the rows it names: move it to box_registry, keep the one-directional edge (answerer imports box_registry), and repoint the two fully-qualified call sites and the intra-doc links. Co-authored-by: gominimal-aw-bot[bot] <281738952+gominimal-aw-bot[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fix(minimald): log the box name plainly in shared-port hand-over lines ( #2308) * fix(minimald): log the box name plainly in shared-port hand-over lines Four log sites in box_actor.rs logged the box name with Rust Debug formatting (name=Some("n2-a8")), which defeats the greps a diagnostics bundle is written against and diverges from the session_name=<plain> convention used by every sibling line. Render the name through as_deref().unwrap_or("-") under the session_name field instead, matching the ANONYMOUS_SESSION idiom in rpc.rs. Tests: pin the rendered hand-over info line and the refused-report warn line to session_name=<plain>. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(minimald): name unnamed boxes with the shared anonymous placeholder The shared-port hand-over lines and the session-start network line now fall back to ANONYMOUS_SESSION ("<anonymous>"), the placeholder every other session_name line uses. The refused host-address create line renders session_name plainly instead of as a Debug-formatted Option. --------- Co-authored-by: gominimal-aw-bot[bot] <281738952+gominimal-aw-bot[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PreviousNext