8000 feat(minimal): name the QUIC block helper in min finalize-install (T88) by minimal-foundry[bot] · Pull Request #2644 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

feat(minimal): name the QUIC block helper in min finalize-install (T88) - #2644

Merged
norrietaylor merged 4 commits into
mainfrom
agent/2607-name-the-quic-block-helper-in-min-finalize-insta
Oct 27, 2026
Merged

norrietaylor merged 4 commits into
mainfrom
agent/2607-name-the-quic-block-helper-in-min-finalize-insta

Conversation

@minimal-foundry
Copy link
Copy Markdown
Contributor

T88 — Name the QUIC block helper in min finalize-install

slice: S12
kind: task
covers: BEP-018
size: S
est: 60 code, 60 tests
executor: agent
difficulty: needs care
model: standard
diagnostics: when the helper or its sudoers drop-in is missing or not root-owned, the item prints: "QUIC block helper: not installed at /usr/local/lib/minimal/quic443-leaf. Run min finalize-install to install it."
observability: none: finalize-install is a one-shot command whose item line is its whole output.
layers:
- finalize:
- crates/minimal/src/cmd/finalize_install.rs: an item quic443-helper that is done when the helper and its sudoers drop-in exist root-owned, and otherwise names the path and the step (#2607)
test: cargo nextest run -p minimal finalize_install_names_the_quic_block_helper
produces: none
consumes: none
proof: min finalize-install shows the QUIC block helper item as done or names its path and the step that installs it.
depends on:
- blocked by #2558


Generated by Claude Code

Proof

On a Linux host with the native provider, min finalize-install --show lists a QUIC block helper item (id quic443-helper in --json). The item shows ✓ when /usr/local/lib/minimal/quic443-leaf and /etc/sudoers.d/minimal-quic443-leaf are root-owned files. Otherwise it shows ✗ with today: not installed at /usr/local/lib/minimal/quic443-leaf. Run min finalize-install to install it. and a step line that names both paths. A run installs them with the classifier step.

Notes from the agent: Design: the classifier step (install-host-classifier.sh --user) is 8000 what installs the helper and its sudoers drop-in, so the new item carries that step. Plan::script now skips a block that is the same as an earlier one, so when both items are missing the step runs once. When the classifier step cannot run here (no cgroup2, run as root, another account's tree), the helper item is blocked with the same cause. The item is offered only where the classifier item is offered (Linux, native provider). plan_for_this_host now puts the names item after the userns profile instead of before the last item, so the order stays userns, names, classifier, quic443-helper. Printed text: the diagnostics wording is copied as written. The summary prints it as ✗ QUIC block helper and then today: not installed at ... Run min finalize-install to install it.. It is not one line with a QUIC block helper: prefix. The fixed wording also has two problems. It names the helper path when only the sudoers drop-in is missing. It also tells the person to run min finalize-install from inside that command's own output. Sudoers check: when /etc/sudoers.d cannot be searched by this account (EACCES), the drop-in counts as present. Without this, the item can never turn done on distributions with a root-only sudoers.d. Also ran cargo nextest run -p minimal finalize_install classifier: 36 passed. Not done: --undo does not change. Docs that list the item ids are outside the layers and are not updated.

Tests

line exit tail
cargo nextest run -p minimal finalize_install_names_the_quic_block_helper 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-fc33... 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

Verify

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

re/compose.rs:402:8
    |
402 | pub fn deferring_env() -> StoredEnv {
    |        ^^^^^^^^^^^^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#must_use_candidate
help: add the attribute
    |
402 + #[must_use]
403 | pub fn deferring_env() -> StoredEnv {
    |

error: this method could have a `#[must_use]` attribute
    --> crates/boxes/src/core/compose.rs:1019:12
     |
1019 |     pub fn lifecycle_hooks_teardown(&self) -> impl DoubleEndedIterator<Item = &ProvenancedHook> {
     |            ^^^^^^^^^^^^^^^^^^^^^^^^
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#must_use_candidate
help: add the attribute
     |
1019 ~     #[must_use]
1020 ~     pub fn lifecycle_hooks_teardown(&self) -> impl DoubleEndedIterator<Item = &ProvenancedHook> {
     |

error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
    --> crates/boxes/src/core/primitives.rs:1085:5
     |
1085 |     pub fn as_sandbox_path(&self) -> &SandboxRelPath {
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
help: remove `must_use`
    --> crates/boxes/src/core/primitives.rs:1084:5
     |
1084 |     #[must_use]
     |     ^^^^^^^^^^^
     = note: alternatively, you may add an explicit reason to the `must_use` attribute
     = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#double_must_use
     = note: `-D clippy::double-must-use` implied by `-D warnings`
     = help: to override `-D warnings` add `#[allow(clippy::double_must_use)]`

error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
    --> crates/boxes/src/core/primitives.rs:1302:5
     |
1302 |     pub fn host_path(&self) -> &HostAbsPath {
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
help: remove `must_use`
    --> crates/boxes/src/core/primitives.rs:1301:5
     |
1301 |     #[must_use]
     |     ^^^^^^^^^^^
     = note: alternatively, you may add an explicit reason to the `must_use` attribute
     = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#double_must_use

error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
    --> crates/boxes/src/core/primitives.rs:1309:5
     |
1309 |     pub fn destination(&self) -> &SandboxRelPath {
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
help: remove `must_use`
    --> crates/boxes/src/core/primitives.rs:1308:5
     |
1308 |     #[must_use]
     |     ^^^^^^^^^^^
     = note: alternatively, you may add an explicit reason to the `must_use` attribute
     = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#double_must_use

error: could not compile `boxes` (lib) due to 6 previous errors

Closes #2607

min finalize-install now lists a quic443-helper item. It is done when
/usr/local/lib/minimal/quic443-leaf and its sudoers drop-in are
root-owned files. Otherwise it names the helper's path and carries the
classifier step, which installs both. The script runs a step that two
items share only once.
@coderabbitai
coderabbitai Bot commented Oct 26, 2026 •
Copy link
Copy Markdown
Contributor
8000

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: df77b6d3-298a-44b8-980a-db52f781f99f

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 26, 2026 •
Copy link
Copy Markdown
Contributor Author

Approved: every review thread resolved at ce05532be6fe.

@norrietaylor resolved the last thread, which releases the approval this review had withheld.

Nothing to do.

@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 · the full ruling

The design does not govern min finalize-install or how a host reports a missing QUIC block helper. It governs the QUIC stance and its enforcement, and this change does not touch them. minimald still fails a launch when the helper refuses an add, so the block still fails closed. The item is in the layer the task names, and it reuses the classifier step that #2601 installed. This is a pass by absence. The summary on the check run lists text and test points for the code review.

Ruled at e176275 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 — 4 findings, 3 inline.

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

The sudoers drop-in no longer counts as present when this account
cannot stat it. In that case sudo -n -l on the helper answers. The
helper and the drop-in must not be symlinks, must carry no group or
other write bit, and must sit in a root-only directory. The classifier
item test gets its doc comment back.
@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 · the full ruling

The change conforms. The new commit makes the QUIC block helper check stricter. It refuses symlinks and writable files and directories. It asks sudo -n -l and does not assume the drop-in is present when /etc/sudoers.d is unreadable. This fails closed, and no check crosses a privilege boundary. The PASS on the earlier head stands for the rest.

Ruled at 61dfce2 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 1 of 10 on the runtime's review findings, pushed as 61dfce24f13f.

line exit tail
cargo nextest run -p minimal finalize_install_names_the_quic_block_helper 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-f804... 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

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

re/compose.rs:402:8
    |
402 | pub fn deferring_env() -> StoredEnv {
    |        ^^^^^^^^^^^^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#must_use_candidate
help: add the attribute
    |
402 + #[must_use]
403 | pub fn deferring_env() -> StoredEnv {
    |

error: this method could have a `#[must_use]` attribute
    --> crates/boxes/src/core/compose.rs:1019:12
     |
1019 |     pub fn lifecycle_hooks_teardown(&self) -> impl DoubleEndedIterator<Item = &ProvenancedHook> {
     |            ^^^^^^^^^^^^^^^^^^^^^^^^
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#must_use_candidate
help: add the attribute
     |
1019 ~     #[must_use]
1020 ~     pub fn lifecycle_hooks_teardown(&self) -> impl DoubleEndedIterator<Item = &ProvenancedHook> {
     |

error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
    --> crates/boxes/src/core/primitives.rs:1085:5
     |
1085 |     pub fn as_sandbox_path(&self) -> &SandboxRelPath {
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
help: remove `must_use`
    --> crates/boxes/src/core/primitives.rs:1084:5
     |
1084 |     #[must_use]
     |     ^^^^^^^^^^^
     = note: alternatively, you may add an explicit reason to the `must_use` attribute
     = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#double_must_use
     = note: `-D clippy::double-must-use` implied by `-D warnings`
     = help: to override `-D warnings` add `#[allow(clippy::double_must_use)]`

error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
    --> crates/boxes/src/core/primitives.rs:1302:5
     |
1302 |     pub fn host_path(&self) -> &HostAbsPath {
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
help: remove `must_use`
    --> crates/boxes/src/core/primitives.rs:1301:5
     |
1301 |     #[must_use]
     |     ^^^^^^^^^^^
     = note: alternatively, you may add an explicit reason to the `must_use` attribute
     = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#double_must_use

error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
    --> crates/boxes/src/core/primitives.rs:1309:5
     |
1309 |     pub fn destination(&self) -> &SandboxRelPath {
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
help: remove `must_use`
    --> crates/boxes/src/core/primitives.rs:1308:5
     |
1308 |     #[must_use]
     |     ^^^^^^^^^^^
     = note: alternatively, you may add an explicit reason to the `must_use` attribute
     = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#double_must_use

error: could not compile `boxes` (lib) due to 6 previous errors

Review threads the coder judged:

  • 👍 valid — PRRT_kwDOPdfcxM6wc3MC: EACCES on the drop-in no longer counts as present. The probe asks sudo -n -l <helper> instead, which works without search access because the rule is NOPASSWD. If sudo says no, the item is missing. (not posted on the thread: not an open thread on the pull request)
  • 👍 valid — PRRT_kwDOPdfcxM6wc3MH: Moved the classifier doc comment back above classifier_item_table. The new test now carries only its own doc. (not posted on the thread: not an open thread on the pull request)
  • 👍 valid — crates/minimal/src/cmd/finalize_install.rs: The check now uses symlink_metadata. It requires uid 0 and mode & 0o022 == 0 on each file and on the directory that holds it.

min finalize-install shows a QUIC block helper item. It is done only when /usr/local/lib/minimal/quic443-leaf and its sudoers drop-in are installed root-owned and root-only, or when sudo confirms the rule. Otherwise it prints "not installed at /usr/local/lib/minimal/quic443-leaf. Run min finalize-install to install it." and carries the classifier step that installs both.

Notes from the agent: Revise round 1. The probe now calls sudo -n -l /usr/local/lib/minimal/quic443-leaf when this account cannot stat the drop-in (EACCES). The host line reads the same items, so on hosts with a root-only /etc/sudoers.d that read also starts sudo once. The parent-directory check covers the directory that holds each file. It does not cover every ancestor. Unit tests do not cover the host probe, because it reads real paths under /usr/local and /etc.

A broad NOPASSWD rule such as ALL let sudo -n -l <helper> succeed with
the drop-in absent. List the rules and require a NOPASSWD entry that
names the helper path.
@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 · the full ruling

This push changes only the sudoers fallback for a root-only /etc/sudoers.d. The item is now done only when a NOPASSWD rule names the helper path. A broad NOPASSWD: ALL no longer counts. This fails closed and matches the rule that the classifier script writes. The PASS on 61dfce24 stands, and the change conforms to §5.3 line 150.

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

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 2 of 10 on the runtime's review findings, pushed as ce05532be6fe.

line exit tail
cargo nextest run -p minimal finalize_install_names_the_quic_block_helper 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-82d8... 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

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

re/compose.rs:402:8
    |
402 | pub fn deferring_env() -> StoredEnv {
    |        ^^^^^^^^^^^^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#must_use_candidate
help: add the attribute
    |
402 + #[must_use]
403 | pub fn deferring_env() -> StoredEnv {
    |

error: this method could have a `#[must_use]` attribute
    --> crates/boxes/src/core/compose.rs:1019:12
     |
1019 |     pub fn lifecycle_hooks_teardown(&self) -> impl DoubleEndedIterator<Item = &ProvenancedHook> {
     |            ^^^^^^^^^^^^^^^^^^^^^^^^
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#must_use_candidate
help: add the attribute
     |
1019 ~     #[must_use]
1020 ~     pub fn lifecycle_hooks_teardown(&self) -> impl DoubleEndedIterator<Item = &ProvenancedHook> {
     |

error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
    --> crates/boxes/src/core/primitives.rs:1085:5
     |
1085 |     pub fn as_sandbox_path(&self) -> &SandboxRelPath {
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
help: remove `must_use`
    --> crates/boxes/src/core/primitives.rs:1084:5
     |
1084 |     #[must_use]
     |     ^^^^^^^^^^^
     = note: alternatively, you may add an explicit reason to the `must_use` attribute
     = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#double_must_use
     = note: `-D clippy::double-must-use` implied by `-D warnings`
     = help: to override `-D warnings` add `#[allow(clippy::double_must_use)]`

error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
    --> crates/boxes/src/core/primitives.rs:1302:5
     |
1302 |     pub fn host_path(&self) -> &HostAbsPath {
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
help: remove `must_use`
    --> crates/boxes/src/core/primitives.rs:1301:5
     |
1301 |     #[must_use]
     |     ^^^^^^^^^^^
     = note: alternatively, you may add an explicit reason to the `must_use` attribute
     = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#double_must_use

error: this function has a `#[must_use]` attribute with no message, but returns a type already marked as `#[must_use]`
    --> crates/boxes/src/core/primitives.rs:1309:5
     |
1309 |     pub fn destination(&self) -> &SandboxRelPath {
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
help: remove `must_use`
    --> crates/boxes/src/core/primitives.rs:1308:5
     |
1308 |     #[must_use]
     |     ^^^^^^^^^^^
     = note: alternatively, you may add an explicit reason to the `must_use` attribute
     = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#double_must_use

error: could not compile `boxes` (lib) due to 6 previous errors

Review threads the coder judged:

  • 👍 valid — crates/minimal/src/cmd/finalize_install.rs:1347: sudo_lets_this_account_run now lists the rules with sudo -n -l and requires a NOPASSWD entry that names the helper path, so a broad NOPASSWD: ALL rule no longer marks the item done. The test pins this.

min finalize-install shows the QUIC block helper item as done only when the helper and its sudoers drop-in are installed root-owned. Where /etc/sudoers.d is root-only, the item is done only when sudo lists a NOPASSWD rule that names /usr/local/lib/minimal/quic443-leaf. Otherwise the item prints "not installed at /usr/local/lib/minimal/quic443-leaf. Run min finalize-install to install it." and carries the step that installs it.

Notes from the agent: Revise round 2. The fallback now runs sudo -n -l with no command and requires a NOPASSWD entry that names the helper path as a whole token. A broad rule such as NOPASSWD: ALL no longer counts. A match on the output of sudo -n -l <path> alone does not fix the finding, because sudo prints the resolved path for any rule that allows it. The test now pins the listing match against the drop-in rule, a rule list that names the helper, ALL rules, a rule without NOPASSWD and a near-miss path. cargo clippy -p minimal --all-targets shows warnings only in crates/boxes, which this change does not touch.

@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.

APPROVE withheld: 2 unresolved threads — minimal-foundry[bot] crates/minimal/src/cmd/finalize_install.rs, minimal-foundry[bot] crates/minimal/src/cmd/finalize_install.rs:1347.

minimal-review — advisory review

Verdict COMMENT — No findings.

Panel: 2 of 3 personas ran (correctness, supply-chain); did not complete: security.

@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 — approved

Verdict APPROVE — every review thread is resolved; @norrietaylor closed the last one. The review of ce05532be6fe withheld this approval on the threads open then. Posted as a comment, as the review itself was; the check and the label carry it.

@minimal-foundry minimal-foundry Bot added the review:approved minimal-review approved the current head label Oct 27, 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.

Own review at ce05532. min finalize-install gains the quic443-helper item. It is done only when the helper and its sudoers drop-in are regular files that root owns and only root can write, in directories that only root can write. Where /etc/sudoers.d cannot be read, it falls back to sudo -n -l, and only a NOPASSWD rule that names the helper itself counts. Otherwise the item names the path and min finalize-install, as T88's diagnostics text says.


Generated by Claude Code

@norrietaylor norrietaylor changed the title T88 — Name the QUIC block helper in min finalize-install feat(minimal): name the QUIC block helper in min finalize-install (T88) Oct 27, 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 ce05532: every merge gate passes at this head (arch PASS at ce05532, zero open threads, every lane aggregator green). Squash title feat(minimal): name the QUIC block helper in min finalize-install (T88) 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 27, 2026
Merged via the queue into main with commit b49feb5 Oct 27, 2026
37 checks passed
@norrietaylor
norrietaylor deleted the agent/2607-name-the-quic-block-helper-in-min-finalize-insta branch October 27, 2026 01:18
norrietaylor added a commit that referenced this pull request Oct 29, 2026
…(0.7.0 backport) (#2742)

* fix(minimal): hold the answerer channel socket owner-only (#2297)

* fix(minimal,minvmd): hold the answerer channel socket owner-only

The box-zone answerer's channel socket was world-connectable: the
launchd plist bound it SockPathMode 0666 and the systemd socket unit
SocketMode=0666, leaving the peer-uid gate the only check on who may
connect. Both units now bind it owner-only -- SockPathOwner the
operator, SockPathMode 0600 on launchd; SocketUser the operator,
SocketMode=0600 on systemd -- with the uid gate kept as defence in
depth.

An install whose socket node carries group or other bits -- one that
predates the mode, or a hand that relaxed it -- now fails custody
through a channel_mode fact, so the advisory re-surfaces and a step
re-run re-binds the socket. The accept loop's foreign-refusal warn is
rate-limited to a few lines a second, so a connect storm cannot flood
the log; the refusals themselves are never limited.

* fix(minimal,minimald,minvmd): name the other account on a refused channel connect

A daemon whose connect the owner-only channel socket refuses now says
the host's DNS service belongs to another account, not a bare
Permission denied. The answerer step needs the operator's account to
resolve, with no stand-in uid, and custody fails a channel socket the
operator does not own.

* fix(minimal,minvmd): leave another operator's DNS service alone

A channel socket owned by another existing, non-root account is that
operator's service: the answerer step is now unavailable with the same
sentence the daemons say on a refused connect, instead of a custody
failure whose step would reinstall the unit and take the service over.
A root-owned socket, or one whose uid names no account, still fails
custody so the step re-binds it.

The minvmd tests that take the process-global foreign-refusal slots
now run one at a time and start and end with every slot free, and the
warn-rate test bounds the burst at two windows' worth of lines, so a
slow runner that spreads the burst does not fail it.

* fix(minimal): offer the step over a socket another account left behind

A channel socket another account owns refuses the answerer step only
when a service is installed; with none, the step's install removes the
leftover socket before it loads the unit, so it is offered.

---------

Co-authored-by: gominimal-aw-bot[bot] <281738952+gominimal-aw-bot[bot]@users.noreply.github.com>
(cherry picked from commit 746770d)

* fix(net): refuse out-of-zone names at a guest deny-subtree DNS listener (#2300)

* fix(net): refuse out-of-zone names at a guest deny-subtree DNS listener

A deny-all host-address box on a VM-backed guest resolved out-of-zone
names at the switch gateway: the interim NET-079 carve-out admitted the
gateway's port 53 whole, UDP and TCP, so a box that admits no destination
could still ask a resolver beyond the machine — and DNS over TCP could
retry past the verdict.

The guest's deny subtree now steers its UDP-53 lookups onto a loopback
listener on the answerer's port (NET-141 slice 2): deny_out admits the
listener beside the reply direction, the dstnat match carries no
destination address (the box asks the gateway like every other
host-address box; the steer is the carve-out), and the listener itself
answers REFUSED for every name outside the box zone — apex and a
non-lowercase zone suffix among them — NODATA for an in-zone non-A
question, and forwards the zone's A questions to the switch gateway
alone. DNS over TCP is neither steered nor admitted.

The guest daemon starts the listener after its classifier table loads;
the daemon's own lookups are untouched (cgroup-keyed match, deny path
only). The interim --gateway-resolver installer flag and every test,
digest and pin that spelled the interim carve-out follow the new shape.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(net): bound and pin the guest DNS listener's forwards

Connect each forward socket to the gateway so a datagram from any other
sender is dropped, cap forwards in flight so a flood cannot exhaust the
daemon's descriptors, and drop the needless to_string calls clippy
rejects in the guest classifier integration test.

* fix(net): refuse questionless and non-QUERY messages at the guest DNS listener

decide() forwarded a message with no question, and an UPDATE or NOTIFY
whose zone section named an in-zone A, because every per-question check
passed vacuously. Answer the first REFUSED and the rest NOTIMP, so the
forward path carries only standard lookups.

Bind the listener directly so its log does not read as the box-zone
answerer, pin the integration test's boot-log check to the exact
example.com REFUSED line, and cite NET-079 (the deny-all case) rather
than NET-141 at the lines this change added.

---------

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>
(cherry picked from commit 2c2f8cc)

* fix(net): spawn the guest DNS tasks with tokio::spawn on release/0.7.0

crate::traced landed on main after the release branch was cut, so the
cherry-pick of #2300 did not compile here.

* build: declare 0.7.3 for the release/0.7.0 backports

v0.7.2 is tagged at the branch head, so the version check requires the
next patch release.

* chore(deps): update pulldown-cmark to 0.13.5 for RUSTSEC-2026-0414

* fix(minimald): close the host classifier's mark, IPv6 and probe gaps (T105) (#2712)

* fix(minimald): close the classifier's mark, IPv6 and probe gaps

The install's conflict scan now treats a ct-mark write with no mask
as a user of every bit, names each rule with its table and chain,
and --ct-mark-mask refuses the ranges Tailscale and kube-proxy class
with. The table refuses non-loopback IPv6 from the boxes subtree and
counts that refusal in the same named counter as the deny chain.

The per-launch probe reads with a UDP datagram, so a launch no longer
waits the SYN retransmit a refused TCP connect pays. The reading at
daemon start keeps its TCP leg. Each probe logs its leg and duration.

* test(minimald): prove the classifier's mark, IPv6 and probe gaps live

Three root-lane proofs load the real table: the install refuses a
whole-mark ct writer and a mask in a well-known range, and an allow
box's IPv6 connection to a peer is refused and counted. The live probe
proof also checks the per-launch reading waits no retransmit.

* fix(minimald): pass shellcheck and pin the new mask refusals

The rerun hint's empty assignment tripped SC1007 in the shell lint
gate. The installer test pinned the old remedy text and escaped with
0x0000c000, the kube-proxy range the install now refuses: it now
pins the new remedy, escapes with 0x03000000, and pins the
well-known range refusal. The IPv6 refusal comment names the
condition that replaces it.

* fix(minimald): re-apply the recorded ct-mark mask and name min finalize-install

With no --ct-mark-mask, the install step adopts the mask a previous
install recorded under the tree. Mask refusals now name
min finalize-install --ct-mark-mask <mask>. The conflict scan joins the
nft and iptables-save hits with a newline.

* feat(minimal): add min finalize-install --ct-mark-mask and keep it at boot

The mask record lives in cgroupfs, so a reboot empties it. The classifier
step's block now passes the chosen or recorded mask to the one-time run
and to the boot unit's ExecStart, so the operator's bits survive a
reboot. A requested mask that differs from the loaded table's re-runs
the step.

* fix(minimald): refuse a CONNMARK --restore-mark that copies the whole mark

The install's ct-mark scan now treats --restore-mark with no --mask,
--ctmask or --nfmask, or an all-ones one, as a whole-mark writer, as it
does --save-mark. A restore with a narrow mask is still no conflict.

* fix(minimald): allow --restore-mark, bind probe datagrams first

A CONNMARK --restore-mark copies the connection mark into the packet
mark and never writes the connection mark, which is all the classifier
reads. This reverts 16d9160 and pins a restore with no mask as no
conflict.

hold_probe_listeners now binds its datagram endpoints before it spawns
any TCP drain thread, so a failed datagram bind returns before a
listener serves.

---------

Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
(cherry picked from commit 354548e)

* fix(minimald): adapt the host classifier gaps backport to release/0.7.0

The cherry-pick before this one keeps main's text for every line #2712
changes. Main's neighbouring code comes from the QUIC block work
(#2513, #2553, #2601, #2644), the box-name placement (#2722) and the
crate renames, none of which release/0.7.0 has. The conflicts were
resolved onto release's code without importing that work:

- classifier.rs: the four new unit tests are appended after release's
  last test, without the QUIC cohort tests main has above them. Their
  sandbox::classifier paths are sandbox2::classifier here.
- install-host-classifier.sh: the table gains the refused counter. The
  QUIC leaf set that main declares beside it is not on release, so it
  stays out. The --help range (2,87p) matches release's header too.
- finalize_install.rs: release has one classifier item, built by
  classifier_item_on_this_host, not main's classifier_items_on_this_host
  with the QUIC block helper item. That function takes --ct-mark-mask
  and fills recorded_mask and requested_mask itself. plan_for_this_host
  keeps release's names item placement and only passes the mask on.
- #2712's edits to finalize_install_names_the_quic_block_helper are
  dropped: release has no QUIC block helper item or test.
- install_test.sh: run_hc puts HC_PATH_PREFIX ahead of PATH, the line
  #2601 added on main. Without it the new --restore-mark case never
  reaches its stand-in iptables-save, and shellcheck flags the variable
  as unused.

The user-facing text is main's, unchanged.

---------

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>
Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review:approved minimal-review approved the current head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

T88 — Name the QUIC block helper in min finalize-install

1 participant

0