8000 feat(minimal): lay out the split VM users, directories and socket modes (T65) by minimal-foundry[bot] · Pull Request #2561 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

feat(minimal): lay out the split VM users, directories and socket modes (T65) - #2561

Merged
norrietaylor merged 11 commits into
mainfrom
agent/2560-run-gvproxy-and-the-vmm-child-as-their-dedicated
Oct 25, 2026
Merged

norrietaylor merged 11 commits into
mainfrom
agent/2560-run-gvproxy-and-the-vmm-child-as-their-dedicated

Conversation

@minimal-foundry
@minimal-foundry minimal-foundry Bot commented Oct 25, 2026 •
Copy link
Copy Markdown
Contributor

T65 — Run gvproxy and the VMM child as their dedicated users

slice: S8
kind: task
covers: BEP-047
size: L
est: 380 code, 320 tests
executor: agent
difficulty: needs care
model: strongest
diagnostics: when minimal-vmm or minimal-gvproxy is missing, or sudo -n cannot run a spawn, the VM host daemon refuses to start with the error the T57 design's ownership model names, on stderr
observability: one info log line at start naming the user gvproxy and the VMM child run as
layers:
- install:
- crates/minimal/src/cmd/finalize_install.rs: creates the minimal-vmm and minimal-gvproxy users and groups, the provider dir at 0710 group minimal-vmm, vmm/, <gvproxy dir>, <gvproxy config dir> and <gate dir> as docs/design/2026-10-25-split-user-vmm-provider-dir.md lays them out, and the sudoers drop-in with the gvproxy, VMM and EGW spawns and the two stop rules (root-owned absolute paths, literal arguments, env_reset with an empty env_keep, umask=0077 with umask_override for gvproxy) (split from T38 on 2026-10-25 by the epic owner)
test: cargo nextest run -p minimal finalize_install_creates_the_split_users_dirs_and_drop_in
- sockets:
- crates/minvmd/src/sock.rs: prepare_socket_dir, restrict_owned_dir and verify_provider_dir_ownership accept 0710 with group minimal-vmm and verify vmm/; enforce_socket_permissions and verify_socket_permissions take the mode and the group from the design's table
test: cargo nextest run -p minvmd split_user_provider_dir_modes_are_enforced
- spawn:
- crates/minvmd/src/cmd/run.rs: spawns gvproxy and the VMM child through sudo -n -u under the drop-in, each in a new process group, never sending SIGKILL to that sudo, with per-run values on descriptor 0; moves ssh.sock, timekeep.sock, boot.log and data-vol.raw to vmm/, the switch socket to <gvproxy dir>, and gvproxy.yaml and gvproxy-bep.sock to <gvproxy config dir>; stops the chmod of ssh.sock; refuses to start when sudo -n fails
- crates/minvmd/src/cmd/vmm_child.rs: creates vmm/boot.log with mode 0640, takes the gate socket path from <gate dir>, and sets the mode of ssh.sock after libkrun binds it
- crates/minvmd/src/volume.rs: create_sparse_raw creates vmm/data-vol.raw with mode 0600, and the supervisor refuses a group or other write bit on either file
- crates/minvmd/src/net.rs: HostGvproxy::spawn starts gvproxy through sudo -n -u minimal-gvproxy, and GvproxyConfig::with_config_path writes gvproxy.yaml at 0640 group minimal-gvproxy
- crates/minvmd/src/control.rs: the guest door admits the minimal-vmm uid and the supervisor's own uid; the host door keeps its check
- crates/minvmd/src/guest_telemetry.rs: accept_loop admits the minimal-vmm uid and the supervisor's own uid
test: cargo nextest run -p minvmd gvproxy_and_vmm_run_as_their_dedicated_users
produces: none
consumes: none
proof: gvproxy runs as minimal-gvproxy and the VMM child as minimal-vmm, each spawned through sudo -n under the drop-in, with the provider dir, vmm/ and the gvproxy dirs at the modes the T57 design names, and the guest and telemetry doors admit the minimal-vmm uid.
depends on:
- blocked by T57

Proof

The proof is NOT delivered in full. gvproxy and the VMM child still run as the login user, not through sudo -n. A person can now see these parts. On Linux with the VM backend, min finalize-install offers a 'vm-users' item. Its script creates minimal-vmm and minimal-gvproxy, sets the provider dir to 0710 group minimal-vmm, creates vmm/ (0750), the gvproxy dir (0750), the gvproxy config dir (0750) and the gate dir (2710). It writes /etc/sudoers.d/minimal-vm and checks it with visudo. minvmd accepts a 0710 provider dir with group minimal-vmm, and it applies the T57 socket table (0660 with the group when the process holds it, else 0600). The guest-control and guest-telemetry doors admit the minimal-vmm uid. The data volume is created 0600 and the boot log 0640.

Notes from the agent: The spawn layer is not done, and it cannot be done inside the task's layers. (1) crates/minimal-client resolves /ssh.sock, so a move of ssh.sock into vmm/ breaks min. (2) cmd/stop.rs signals vmm_pid directly. A VMM child under another user needs a stop-rule subcommand (proposed name __stop-group) that does not exist. (3) The EGW process (proposed subcommand __egw) does not exist. The supervisor runs as the login and cannot bind in a gate dir that minimal-gvproxy owns. (4) vm.rs, state.rs, net/shuttle.rs (gate_sock_beside), telemetry.rs and image.rs read the per-run values from the environment. A move to descriptor 0 (sudo env_reset) needs changes in those files. (5) The installer puts binaries in user-writable paths such as ~/.local/bin, so a default install cannot get a sudo rule. The vm-users item reports 'cannot' in that case. (6) A home directory ancestor at 0700 or 0750 blocks minimal-vmm from the provider dir. The script walks the path and refuses. Not done for these reasons: the sudo -n spawns, the sudo -n refusal on stderr, the info log line that names the users, the moves to vmm/ and the gvproxy dirs, GvproxyConfig with_config_path at 0640, the supervisor …[truncated]

Tests

line exit tail
cargo nextest run -p minimal finalize_install_creates_the_split_users_dirs_and_drop_in 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-721e... Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling paths v0.0.1 (/workbench/crates/paths) Compiling stdlib v0.0.22 (/workbenc
cargo nextest run -p minvmd split_user_provider_dir_modes_are_enforced 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-3238... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling version v0.8.0 (/workbenc
cargo nextest run -p minvmd gvproxy_and_vmm_run_as_their_dedicated_users 4 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-0e68... Compiling version v0.8.0 (/workbench/crates/version) Compiling mlog v0.0.1 (/workbench/crates/mlog) Compiling minvmd v0.8.0 (/workbench/crates/minvmd) Finished 'test' profil

Verify

min task run agent-verify exited 101 under rustc 1.98.1, which the repository does not pin (1.97.0 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

the sandbox ran rustc (1.98.1), the repository pins 1.97.0 in rust-toolchain.toml; a failure here may be the newer toolchain's, not this change's
Applying loadouts: default
Running task runtime-implement-verify in session task-runtime-implement-verify-b184...
    Checking opentelemetry v0.33.1
    Checking ot v0.0.1 (/workbench/crates/ot)
    Checking args v0.0.1 (/workbench/crates/args)
    Checking paths v0.0.1 (/workbench/crates/paths)
   Compiling version v0.8.0 (/workbench/crates/version)
    Checking switch v0.0.1 (/workbench/crates/switch)
   Compiling minvmd v0.8.0 (/workbench/crates/minvmd)
    Checking opentelemetry_sdk v0.33.1
    Checking tracing-opentelemetry v0.34.0
    Checking opentelemetry-http v0.33.1
    Checking opentelemetry-appender-tracing v0.33.1
    Checking google-cloud-gax-internal v0.7.20
    Checking opentelemetry-proto v0.33.0
   Compiling stdlib v0.0.22 (/workbench/crates/stdlib)
   Compiling serial_test_derive v4.0.1
    Checking opentelemetry-otlp v0.33.0
    Checking diagnostics v0.0.1 (/workbench/crates/diagnostics)
    Checking google-cloud-longrunning v1.14.0
    Checking google-cloud-iam-v1 v1.13.0
    Checking mlog v0.0.1 (/workbench/crates/mlog)
    Checking google-cloud-lro v1.11.0
    Checking bep v0.0.1 (/workbench/crates/bep)
    Checking serial_test v4.0.1
    Checking google-cloud-storage v1.20.0
    Checking async-dialog v0.0.1 (/workbench/crates/async-dialog)
    Checking common v0.0.1 (/workbench/crates/common)
    Checking boxes v0.0.1 (/workbench/crates/boxes)
    Checking sandbox v0.0.1 (/workbench/crates/sandbox)
    Checking minimald-rpc v0.0.1 (/workbench/crates/minimald-rpc)
    Checking mfile v0.0.1 (/workbench/crates/mfile)
    Checking checkouts v0.0.1 (/workbench/crates/checkouts)
    Checking decode v0.0.1 (/workbench/crates/decode)
error: using `chunks_exact` with a constant chunk size
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    |
help: consider using `as_chunks::<2>()` instead
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#chunks_exact_to_as_chunks
    = note: `-D clippy::chunks-exact-to-as-chunks` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::chunks_exact_to_as_chunks)]`

error: could not compile `minimald-rpc` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...

Base

The base branch moved while the coder worked; it was merged in before the tests ran.

Residuals

The epic owner narrowed T65 (#2560) on 2026-10-25 to the foundation this PR delivers: the split users, directories and socket modes. Running gvproxy and the VMM child as their own users moved to new tasks.

Closes #2560


Generated by Claude Code

Adds a vm-users item that creates minimal-vmm and minimal-gvproxy, the
provider dir at 0710 group minimal-vmm, vmm/, the gvproxy, gvproxy config
and gate dirs, and a sudoers drop-in with the spawns and the stop rules.
The provider dir may be 0710 with group minimal-vmm, vmm/ is verified and
never created, and each socket takes its mode and group from the design's
table, staying 0600 while this process does not hold the group.
The guest-control and guest-telemetry doors admit the minimal-vmm uid
beside the daemon's own. The data volume is created 0600 and the boot
log 0640, whatever the umask gives.
@coderabbitai
coderabbitai Bot commented Oct 25, 2026 •
Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

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: Advanced
  • Run ID: ed4e8838-2d09-440b-b757-88a339a76833

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

Comment @coderabbitai help to get the list of available commands.

@minimal-foundry
minimal-foundry Bot commented Oct 25, 2026 •
Copy link
Copy Markdown
Contributor Author

Reviewed: 1 finding, none blocking at 8f77c1f9e3b4.

Address the comments on the diff.

Clobber: none seen.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

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 — 4 findings, 4 inline.

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

@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

arch-review: CONDITIONS · 3 conditions · the full ruling

The parts that landed match the T57 layout, but the task proof is not delivered. gvproxy and the VMM child still run as the login user. Do not close #2560, and file each undone item as an issue (C1). The root script in vm_users_script() tests for a symlink and then runs cd -P in a separate step. A login process can swap in a symlink between the two steps, so root can give a root-owned directory to minimal-vmm. Add the parent-inode check in C2. Do not offer the vm-users item yet (C3), because its sudo rules name subcommands that minvmd does not have. Two plan questions go to a person as escalations.

Ruled at c2a33a5 against gominimal/arch@109541a48c31 by claude-opus-5-5.

The switch config is created fresh with O_NOFOLLOW at mode 0640, and
its group is minimal-gvproxy when this process holds that group. A
symlink or an entry with another owner at the path is refused.
Copy link
Copy Markdown
Member

/revise

Round 1 of #2561, on the arch ruling at c2a33a5 and four review findings. No round is in flight. The epic owner narrowed T65 on 2026-10-25 to the foundation this PR delivered (#2560 and the plan comment now read "Lay out the split users, directories and socket modes for a split-user VMM"). The sudo -n spawns, the moves into vmm/ and the gvproxy dirs, per-run values on descriptor 0, the __stop-group stop rule, the minimal-client ssh path and the root migration of data-vol.raw are now T66 (#2566), and the EGW process stays T38 (#2285). Both wait on gominimal/arch#143 (where the binaries and the provider dir live for a per-user install). Do not start any of that work here, and remove crates/minvmd/src/cmd/run.rs and crates/minvmd/src/net.rs from this PR if it changes them.

  1. Arch C2 (threads 4371564628 and 4371564630) and findings 4371555847 and 4371555861 (HIGH): in vm_users_script() in crates/minimal/src/cmd/finalize_install.rs, the [ ! -L ... ] test and the cd -P that follows are two steps, so a login process can swap in a symlink between them. Before each cd -P in the component walk and before cd -P vmm, record here=$(stat -c %d:%i .); after the cd -P, run [ "$(stat -c %d:%i ..)" = "$here" ] || { echo "<path>: a component moved" >&2; exit 1; }. Add a test that the script carries this check after every cd -P.
  2. Arch C3 (threads 4371564622 and 4371564626): in other_items_for_this_host() remove items.push(linux::vm_users_item_on_this_host()); so the item is not offered at this head. Keep vm_users_item_over, sudoers_drop_in and their tests. T66 adds the line back with the spawns, and fixes the __krun-vmm <vm> rule against KrunVmm.
  3. Finding 4371555868 (gpasswd -M): the T57 design gives each split group exactly one login member, so check the finding against the design first. Never remove another login or an admin-added member silently: if the group already holds a different login, refuse with a message that names it, or add with gpasswd -a and keep one drop-in per login if the design allows it. Say which on the thread and pin it with a test.
  4. Finding 4371555872 and arch C1: rewrite the PR body so the Proof matches the narrowed T65 (the users, directories, socket modes and door peer checks it lays out) and claims no spawn. Keep Closes #2560. Under ## Residuals, name T66 — Spawn gvproxy and the VMM child through sudo -n as their dedicated users #2566 (T66, the spawns and moves) and gominimal/arch#143.
  5. Reply on each thread naming the commit and resolve the ones the commit fixes.

One commit, Conventional Commits subject, body lines at most 72 characters.


Generated by Claude Code

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

That /revise is held, not dropped. A coder is already running on #2560. Your note is kept and goes into the next round here, which starts when that coder finishes.

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.

The login owns the provider dir and can swap an entry for a symlink
between the -L test and the cd. Root now compares the physical path
with the path it means after each cd, vmm/ included, before it changes
anything.
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

arch-review: CONDITIONS · 2 conditions · the full ruling

The two fixes in this push conform. The pwd -P check pins each directory before root changes it. write_config writes gvproxy.yaml as the repo design says. But gvproxy and the VMM child still run as the login, so Gatehouse §6.10 line 831 is not met. The undone work has no issues. C1 files each item as a residual. C2 keeps #2560 open and marks the proof leg not_run. Two questions go to a person: the split of T65, and custody of a default install in ~/.local/bin.

Ruled at 1c004ea against gominimal/arch@109541a48c31 by claude-opus-5-5.

Comment thread crates/minvmd/src/net.rs Resolved
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

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 — 6 findings, 6 inline.

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

Comment thread crates/minvmd/src/net.rs Resolved
Comment thread crates/minvmd/src/control.rs Resolved
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on the runtime's review findings, pushed as 1c004eaa1b81.

line exit tail
cargo nextest run -p minimal finalize_install_creates_the_split_users_dirs_and_drop_in 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-d93c... Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling paths v0.0.1 (/workbench/crates/paths) Compiling stdlib v0.0.22 (/workbenc
cargo nextest run -p minvmd split_user_provider_dir_modes_are_enforced 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-40ce... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling version v0.8.0 (/workbenc
cargo nextest run -p minvmd gvproxy_and_vmm_run_as_their_dedicated_users 4 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-fd3f... Compiling version v0.8.0 (/workbench/crates/version) Compiling mlog v0.0.1 (/workbench/crates/mlog) Compiling minvmd v0.8.0 (/workbench/crates/minvmd) Finished 'test' profil

Verify min task run agent-verify exited 101 under rustc 1.98.1, which the repository does not pin (1.97.0 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

the sandbox ran rustc (1.98.1), the repository pins 1.97.0 in rust-toolchain.toml; a failure here may be the newer toolchain's, not this change's
Applying loadouts: default
Running task runtime-implement-verify in session task-runtime-implement-verify-fee9...
    Checking args v0.0.1 (/workbench/crates/args)
    Checking ot v0.0.1 (/workbench/crates/ot)
    Checking paths v0.0.1 (/workbench/crates/paths)
   Compiling version v0.8.0 (/workbench/crates/version)
    Checking switch v0.0.1 (/workbench/crates/switch)
    Checking common v0.0.1 (/workbench/crates/common)
   Compiling minvmd v0.8.0 (/workbench/crates/minvmd)
   Compiling stdlib v0.0.22 (/workbench/crates/stdlib)
    Checking boxes v0.0.1 (/workbench/crates/boxes)
    Checking sandbox v0.0.1 (/workbench/crates/sandbox)
    Checking mlog v0.0.1 (/workbench/crates/mlog)
    Checking diagnostics v0.0.1 (/workbench/crates/diagnostics)
    Checking bep v0.0.1 (/workbench/crates/bep)
    Checking async-dialog v0.0.1 (/workbench/crates/async-dialog)
    Checking minimald-rpc v0.0.1 (/workbench/crates/minimald-rpc)
    Checking mfile v0.0.1 (/workbench/crates/mfile)
    Checking checkouts v0.0.1 (/workbench/crates/checkouts)
    Checking decode v0.0.1 (/workbench/crates/decode)
    Checking graph v0.0.1 (/workbench/crates/graph)
    Checking lcache v0.0.1 (/workbench/crates/lcache)
error: using `chunks_exact` with a constant chunk size
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    |
help: consider using `as_chunks::<2>()` instead
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#chunks_exact_to_as_chunks
    = note: `-D clippy::chunks-exact-to-as-chunks` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::chunks_exact_to_as_chunks)]`

error: could not compile `minimald-rpc` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...

Converted to a draft: something above needs a person before review.

Review threads the coder judged:

  • 👍 valid — crates/minimal/src/cmd/finalize_install.rs:1604: After every cd -P in the walk, vm_users_script now requires pwd -P to equal the expected physical path, vmm/ included. A symlink swapped in after the -L test exits non-zero before any chown or chmod.
  • 👍 valid — PRRT_kwDOPdfcxM6wC9EO: After cd -P vmm, root now requires pwd -P to be /vmm, so a swap to /etc or any other root-owned target is refused before chown/chmod. Owner 0 is still accepted there: the design (step 2) lets a run that stopped part-way leave vmm/ root-owned at 0700. (not posted on the thread: not an open thread on the pull request)
  • 👎 invalid — crates/minimal/src/cmd/finalize_install.rs:1587: This is deliberate. The design (Names, minimal-vmm group) says the group has exactly one login member, and finalize-install removes any other member. A second login member could connect to the first login's 0660 VM sockets. gpasswd -a and one drop-in per login would let two logins share both groups,
  • 👍 valid — crates/minimal/src/cmd/finalize_install.rs:1357: Correct: nothing spawns through sudo on this branch. This round adds only the gvproxy.yaml 0640/minimal-gvproxy writer from that layer. The result now says plainly that gvproxy and the VMM child do not yet run as their own users. It also lists what the spawn needs outside this task's files: the EGW

The proof is only partly true at this branch. A person can see that min finalize-install creates minimal-vmm and minimal-gvproxy, the provider dir at 0710 group minimal-vmm, vmm/, the gvproxy, gvproxy config and gate dirs, and the sudoers drop-in. The root walk now checks pwd -P after each cd. The sockets take the modes and groups of the design. gvproxy.yaml is written 0640 with group minimal-gvproxy. The guest and telemetry doors admit the minimal-vmm uid. gvproxy and the VMM child do NOT run as their dedicated users yet. Nothing spawns through sudo -n on this branch.

Notes from the agent: Revise round 1. Threads 1 and 2: the vm-users script now compares pwd -P with the expected physical path after each cd -P, vmm/ included, so a symlink swapped in after the -L test is refused before root changes anything. Thread 3: no change, see its note. Thread 4: the finding holds. The PR body must not claim the spawn layer. This round added one in-layer part of it: GvproxyConfig::write_config writes gvproxy.yaml with O_NOFOLLOW, mode 0640 and group minimal-gvproxy when the process holds it, and refuses a symlink or a foreign entry (test net::tests::gvproxy_config_is_written_0640_with_the_gvproxy_group).

The rest of the spawn layer cannot be done inside its layers without breaking the VM. I left it undone on purpose:

  1. The drop-in admits minvmd __krun-vmm <vm> and minvmd __stop-group <vm>. main.rs (outside the layers) has no VM-name argument on __krun-vmm and no __stop-group subcommand. Without a stop rule, a stuck gvproxy or VMM child under sudo has no stop, because the supervisor must not SIGKILL its sudo.
  2. Under the design, the egress gate and switch::bep_host run in the EGW process as minimal-gvproxy. No task layer here creates that process (net/egress_gate.rs, a new __egw subcommand). With the gate still in the supervisor, gvproxy as minimal-gvproxy gives a 0600 switch socket that the supervisor cannot dial, and the supervisor cannot bind the gate …[truncated]

@minimal-foundry
minimal-foundry Bot marked this pull request as draft October 25, 2026 17:11
The vm-users script now keeps the device and inode of . before each
cd -P, and requires them at .. after it. A symlink swapped in between
the -L test and the cd stops the script before root changes anything.

The item is no longer offered: its drop-in names minvmd subcommands
that do not exist yet. T66 (#2566) adds it back with the sudo spawns.

The item refuses when a split group has another member, and names it.
The script adds the login with gpasswd -a and removes nobody. It also
refuses when the provider dir does not exist yet. sudoers_arg rejects
the sudoers wildcard characters.

vmm_uid in minvmd no longer caches a miss.

The gvproxy.yaml writer in net.rs leaves this PR. It moves to T66.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 2 of 10 on a /revise note, pushed as 89cacf7e74db.

line exit tail
cargo nextest run -p minimal finalize_install_creates_the_split_users_dirs_and_drop_in 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-ae11... Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling paths v0.0.1 (/workbench/crates/paths) Compiling stdlib v0.0.22 (/workbenc
cargo nextest run -p minvmd split_user_provider_dir_modes_are_enforced 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-60bb... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling version v0.8.0 (/workbenc

Verify min task run agent-verify exited 101 under rustc 1.98.1, which the repository does not pin (1.97.0 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

the sandbox ran rustc (1.98.1), the repository pins 1.97.0 in rust-toolchain.toml; a failure here may be the newer toolchain's, not this change's
Applying loadouts: default
Running task runtime-implement-verify in session task-runtime-implement-verify-efc4...
    Checking args v0.0.1 (/workbench/crates/args)
    Checking ot v0.0.1 (/workbench/crates/ot)
    Checking paths v0.0.1 (/workbench/crates/paths)
   Compiling version v0.8.0 (/workbench/crates/version)
    Checking switch v0.0.1 (/workbench/crates/switch)
   Compiling minvmd v0.8.0 (/workbench/crates/minvmd)
    Checking common v0.0.1 (/workbench/crates/common)
   Compiling stdlib v0.0.22 (/workbench/crates/stdlib)
    Checking diagnostics v0.0.1 (/workbench/crates/diagnostics)
    Checking mlog v0.0.1 (/workbench/crates/mlog)
    Checking bep v0.0.1 (/workbench/crates/bep)
    Checking async-dialog v0.0.1 (/workbench/crates/async-dialog)
    Checking boxes v0.0.1 (/workbench/crates/boxes)
    Checking sandbox v0.0.1 (/workbench/crates/sandbox)
    Checking minimald-rpc v0.0.1 (/workbench/crates/minimald-rpc)
    Checking mfile v0.0.1 (/workbench/crates/mfile)
    Checking checkouts v0.0.1 (/workbench/crates/checkouts)
    Checking decode v0.0.1 (/workbench/crates/decode)
    Checking graph v0.0.1 (/workbench/crates/graph)
    Checking lcache v0.0.1 (/workbench/crates/lcache)
    Checking rcache v0.0.1 (/workbench/crates/rcache)
    Checking op v0.0.1 (/workbench/crates/op)
error: using `chunks_exact` with a constant chunk size
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    |
help: consider using `as_chunks::<2>()` instead
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#chunks_exact_to_as_chunks
    = note: `-D clippy::chunks-exact-to-as-chunks` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::chunks_exact_to_as_chunks)]`

error: could not compile `minimald-rpc` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...

Review threads the coder judged:

  • 👍 valid — crates/minimal/src/cmd/finalize_install.rs: 89cacf7 removes the push from other_items_for_this_host, so finalize-install does not offer vm-users at this head. T66 (T66 — Spawn gvproxy and the VMM child through sudo -n as their dedicated users #2566) adds it back with the sudo -n spawns.
  • 👍 valid — crates/minimal/src/cmd/finalize_install.rs:1529: 89cacf7 keeps the rule text but no longer installs it, because the item is not offered. T66 (T66 — Spawn gvproxy and the VMM child through sudo -n as their dedicated users #2566) fixes the __krun-vmm rule against KrunVmm when it changes the spawn.
  • 👍 valid — crates/minimal/src/cmd/finalize_install.rs:1621: 89cacf7 records here=$(stat -c %d:%i .) before cd -P vmm and requires stat -c %d:%i .. to match after it. If it does not match, the script stops with "/vmm moved".
  • 👍 valid — crates/minimal/src/cmd/finalize_install.rs:1583: 89cacf7 adds the same parent-inode check after every cd -P in the component walk. A test pins it for all 8 cd -P lines.
  • 👍 valid — crates/minimal/src/cmd/finalize_install.rs:1528: The rules are no longer installed at this head (89cacf7). The PR residuals name __stop-group under T66 (T66 — Spawn gvproxy and the VMM child through sudo -n as their dedicated users #2566) and __egw under T38 (T38 — Run the egress gate and bep_host in an EGW process apart from the proxy #2285). The coder has no credential to file the issues.
  • 👍 valid — crates/minvmd/src/net.rs:257: 89cacf7 reverts net.rs to main, so the writer leaves this PR. The move of gvproxy.yaml to is a residual under T66 (T66 — Spawn gvproxy and the VMM child through sudo -n as their dedicated users #2566).
  • 👍 valid — crates/minimal/src/cmd/finalize_install.rs:1586: 89cacf7 makes the item refuse, with no script, when the provider dir does not exist. The rest of the plan still runs. A test pins it.
  • 👍 valid — crates/minimal/src/cmd/finalize_install.rs: 89cacf7 replaces gpasswd -M with gpasswd -a, behind a guard that stops the script on any other member. The item refuses and names the other member and the sudo gpasswd -d command. The design allows one login per host, so the drop-in stays one file.
  • 👎 invalid — crates/minvmd/src/net.rs: This PR no longer changes crates/minvmd/src/net.rs. 89cacf7 reverts it to main per the narrowed T65. The gvproxy.yaml writer and its missing-group error belong to T66 (T66 — Spawn gvproxy and the VMM child through sudo -n as their dedicated users #2566).
  • 👍 valid — crates/minimal/src/cmd/finalize_install.rs:1498: In 89cacf7, sudoers_arg rejects * ? [ ] and ! as well. A test pins each one.
  • 👍 valid — crates/minimal/src/cmd/finalize_install.rs:1766: The login cannot stat inside /etc/sudoers.d. So 89cacf7 accepts the gap in the VM_USERS_RECORD comment: a changed drop-in reads as done, and the sudo -n spawn then fails.
  • 👍 valid — crates/minvmd/src/control.rs: In 89cacf7, vmm_uid caches only a uid it finds in a OnceLock. A miss is looked up again on the next connection.

At this branch, the vm-users script in min finalize-install creates the minimal-vmm and minimal-gvproxy users and groups. It sets the provider dir to 0710 with group minimal-vmm and sets up vmm/, the gvproxy dir, the gvproxy config dir and the gate dir at the T57 design's modes. It also renders the sudoers drop-in. After every cd -P, the script checks the parent inode. minvmd accepts and enforces those modes. The guest and telemetry doors admit the minimal-vmm uid. The item is not offered yet, so no spawn runs as the split users. That is T66 (#2566).

Notes from the agent: Commit 89cacf7. Directive item 1: the script keeps here=$(stat -c %d:%i .) before each cd -P (the component walk and vmm). After each cd -P it requires stat -c %d:%i .. to give the same value. The pwd -P check stays too. A test pins this for all 8 cd -P lines. Item 2: other_items_for_this_host no longer pushes the vm-users item. vm_users_item_on_this_host keeps #[allow(dead_code)] with a comment until T66 adds the caller back. Item 3: the design says each group has exactly one login member and that finalize-install removes any other member. I followed the directive and did not do that. The item now refuses with a message that names the other members and the sudo gpasswd -d command. The script uses gpasswd -a with a member guard and removes nobody. The design text at docs/design/2026-10-25-split-user-vmm-provider-dir.md lines 78-79 still says finalize-install removes other members. That doc is outside this task's layers, so a follow-up must align it. Also from the threads: the item refuses when the provider dir does not exist yet, sudoers_arg rejects * ? [ ] !, minvmd's vmm_uid no longer caches a miss, and a comment on VM_USERS_RECORD accepts the record/drop-in gap. crates/minvmd/src/net.rs is reverted to main, so the gvproxy.yaml writer leaves this PR (directive). crates/minvmd/src/cmd/run.rs was not changed by this PR. Item 4 (PR body) for the runner: the Proof is the sentenc…[truncated]

@norrietaylor
norrietaylor marked this pull request as ready for review October 25, 2026 17:59
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

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 — 3 findings, 3 inline.

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

Comment thread crates/minvmd/src/control.rs Resolved
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

arch-review: PASS_WITH_CONDITIONS · 1 condition · the full ruling

This head conforms for the narrowed T65 scope. The live changes fail closed. The guest doors admit minimal-vmm only when the user exists, and the host door keeps its check. A socket opens to a group only when the process holds that group. The vm-users root step is not offered, so no drop-in names the missing subcommands. The process separation in Gatehouse §6.10 line 831 is residual #2566. C1: name in the #2566 residual the work it does not name now. The biggest item is the root move of data-vol.raw into vmm/.

Ruled at 89cacf7 against gominimal/arch@109541a48c31 by claude-opus-5-5.

…T65)

The vm-users script prints the provider path in double quotes, so a
path with $, a backtick or a backslash is now refused. The item works
on the canonical provider dir, so a symlinked /home does not fail the
walk. The guest door tests the own uid before the minimal-vmm lookup.
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

arch-review: PASS_WITH_CONDITIONS · 1 condition · the full ruling

The compare conforms. The new path refusal and the canonical provider path keep the root script literal and under custody (§7.1 line 258). The script still walks the real path and checks each owner. The guest door admits the same uids as before. BEP-047 stays open until T66 (#2566) adds the sudo -n spawns and gives the vm-users item its caller back. C1 records this.

Ruled at 8f77c1f against gominimal/arch@109541a48c31 by claude-opus-5-5.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

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 — 1 finding, 1 inline.

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

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 3 of 10 on the runtime's review findings, pushed as 8f77c1f9e3b4.

line exit tail
cargo nextest run -p minimal finalize_install_creates_the_split_users_dirs_and_drop_in 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-557c... Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling paths v0.0.1 (/workbench/crates/paths) Compiling stdlib v0.0.22 (/workbenc
cargo nextest run -p minvmd split_user_provider_dir_modes_are_enforced 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-3b9e... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling version v0.8.0 (/workbenc

Verify min task run agent-verify exited 101 under rustc 1.98.1, which the repository does not pin (1.97.0 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

the sandbox ran rustc (1.98.1), the repository pins 1.97.0 in rust-toolchain.toml; a failure here may be the newer toolchain's, not this change's
Applying loadouts: default
Running task runtime-implement-verify in session task-runtime-implement-verify-897c...
    Checking ot v0.0.1 (/workbench/crates/ot)
    Checking args v0.0.1 (/workbench/crates/args)
    Checking paths v0.0.1 (/workbench/crates/paths)
   Compiling version v0.8.0 (/workbench/crates/version)
    Checking switch v0.0.1 (/workbench/crates/switch)
   Compiling minvmd v0.8.0 (/workbench/crates/minvmd)
    Checking common v0.0.1 (/workbench/crates/common)
   Compiling stdlib v0.0.22 (/workbench/crates/stdlib)
    Checking diagnostics v0.0.1 (/workbench/crates/diagnostics)
    Checking boxes v0.0.1 (/workbench/crates/boxes)
    Checking sandbox v0.0.1 (/workbench/crates/sandbox)
    Checking bep v0.0.1 (/workbench/crates/bep)
    Checking mlog v0.0.1 (/workbench/crates/mlog)
    Checking async-dialog v0.0.1 (/workbench/crates/async-dialog)
    Checking minimald-rpc v0.0.1 (/workbench/crates/minimald-rpc)
    Checking mfile v0.0.1 (/workbench/crates/mfile)
    Checking decode v0.0.1 (/workbench/crates/decode)
    Checking checkouts v0.0.1 (/workbench/crates/checkouts)
    Checking graph v0.0.1 (/workbench/crates/graph)
    Checking lcache v0.0.1 (/workbench/crates/lcache)
    Checking rcache v0.0.1 (/workbench/crates/rcache)
    Checking op v0.0.1 (/workbench/crates/op)
    Checking orchestrator v0.0.1 (/workbench/crates/orchestrator)
    Checking check v0.0.1 (/workbench/crates/check)
error: using `chunks_exact` with a constant chunk size
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    |
help: consider using `as_chunks::<2>()` instead
   --> crates/minimald-rpc/src/trace.rs:199:46
    |
199 |     for (byte, pair) in out.iter_mut().zip(s.chunks_exact(2)) {
    |                                              ^^^^^^^^^^^^^^^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#chunks_exact_to_as_chunks
    = note: `-D clippy::chunks-exact-to-as-chunks` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::chunks_exact_to_as_chunks)]`

error: could not compile `minimald-rpc` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...

Review threads the coder judged:

  • 👍 valid — PRRT_kwDOPdfcxM6wDlL-: vm_users_item_over now refuses a provider or VM dir path that holds $, a backtick or a backslash, so the double-quoted echo lines cannot expand it. The test covers all three characters. (not posted on the thread: not an open thread on the pull request)
  • 👍 valid — crates/minimal/src/cmd/finalize_install.rs:1584: vm_users_item_on_this_host canonicalizes the provider dir before it builds the layout and the walk. The drop-in does not name that path, so the sudo rules do not change.
  • 👍 valid — PRRT_kwDOPdfcxM6wDlMK: check_guest_peer_uid tests peer_uid == my_uid first, so vmm_uid() and its getpwnam_r lookup run only for another uid. (not posted on the thread: not an open thread on the pull request)

The vm-users script in min finalize-install lays out the split users and directories at the T57 design's modes. The item now refuses a provider path that the shell can expand, and it works on the real path when /home is a symlink. minvmd accepts and enforces the design's modes. The guest and telemetry doors admit the minimal-vmm uid, and they look that uid up only when the peer is not the daemon's own uid. The vm-users item is still not offered until T66.

Notes from the agent: Revise round 3. All three findings are valid, and each one has a fix. The test now covers paths with $(...), a backtick and a backslash. The refusal cause text changed to name the setup script and the new characters. cargo clippy -p minimal -p minvmd --all-targets showed warnings only in minimald and minimald-rpc, which this change does not touch. just clippy-strict was not run.

@norrietaylor norrietaylor changed the title T65 — Run gvproxy and the VMM child as their dedicated users feat(minimal): lay out the split VM users, directories and socket modes (T65) Oct 25, 2026
@norrietaylor norrietaylor 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.

Babysitter approval at 8f77c1f: every merge gate passes at this head (arch PASS at 8f77c1f, zero open threads, every lane aggregator green). Squash title feat(minimal): lay out the split VM users, directories and socket modes (T65) and the squashed message pass commitlint. Queued for squash merge.


Generated by Claude Code

@norrietaylor
norrietaylor added this pull request to the merge queue Oct 25, 2026
Merged via the queue into main with commit be9202e Oct 25, 2026
37 checks passed
@norrietaylor
norrietaylor deleted the agent/2560-run-gvproxy-and-the-vmm-child-as-their-dedicated branch October 25, 2026 23:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

T65 — Lay out the split users, directories and socket modes for a split-user VMM

1 participant

0