Plan, Phase 0, and the #191 trust model revision #1
Loading…
Reference in a new issue
No description provided.
Delete branch "claude/plan-rev-191"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The plan, Phase 0, and a revision of the plan for oap/nix-config#191.
Commits
59192eaadds PLAN.md: a Forgejo Runner v13.1 plugin that runs each Actions job in its own Firecracker microVM.15fdfa7Phase 0: Go module with the vendoredplugin.proto, a stub daemon,flake.nixwith the checks (lint, lint-nix, proto-drift, license-boundary, unit, kvm-smoke), the workflows and the review prompt.851a4d8revises the plan after the oap/nix-config#191 test:nix:hostjob is not a boundary. The plugin gets its own runner instance and uid, with onlyfirecracker://labels. Its socket belongs to that uid's group, with aSO_PEERCREDcheck. End state: removenix:host.oap(oap/nix-config#130, oap/nix-config#190).dockerlabel. The VM tier runs by hand from the Mac before a merge. Stage 2 (after dogfooding) moves all jobs to Firecracker.nix:hostand a rotation of the secrets.leftover.yamlandtoken-probe.yaml. §8 has new open questions: nix in the guest, and egress per label.--builders ssh-ng://nixosfails because oap/nix-config#178 removed@wheelfromtrusted-users.scripts/vm-checkis replaced by--eval-store auto --store ssh-ng://nixos(README).49f7c03records in PLAN.md §7.5 that this repo has its own review token.474acf9takesclaude-codefrom a pinnednixpkgs-masterinput. nixos-26.05 has 2.1.223, andclaude-opus-5-5needs 2.1.280 or newer.9e2b186fixes the first review's findings:iifname "fc-*". No IPv6 in v1. DNS defaults to public resolvers, not MagicDNS.vm-isolationcovers the tailnet, MagicDNS and IPv6, and its fake upstream has a public address.license-boundaryfails whengo listfails.Review token
This repo has its own
CLAUDE_CODE_OAUTH_TOKEN, separate from nix-config's. nas provisions it (oap/nix-config#193, deployed 2026-10-08). Anyone with write access to this repo, Renovate included, can read it. The review job runs on thedockerlabel, so leftover host processes (oap/nix-config#191) cannot read it.Tests
lint,lint-nix,proto-drift,license-boundary,unitandkvm-smokepass with--store ssh-ng://nixos.fastandreviewpass on thedockerlabel with thenixos/nixcontainer.🤖 Generated with Claude Code
Claude review
Two things to fix before merge: the planned VM firewall leaves the host's tailnet and IPv6 reachable from guests, and the description and PLAN.md disagree about whether this repo has a review token. No problems found in the Phase 0 code beyond one check that can pass when it should fail.
1. Guests can reach the tailnet and IPv6 (PLAN.md:370–371, :730)
§3.4 masquerades guest traffic out of the host. It then drops only 169.254.0.0/16 and the RFC1918 ranges. §7.1 says the host has tailnet access. Two gaps follow:
/etc/resolv.confpoints there, the §3.4 DNS default sends guests to it.inetbut the rules list no IPv6 ranges. fc00::/7, fe80::/10 and the host's own v6 addresses are not covered.What to change in the plan:
dockerlabel already does ("TCP 80/443 to public IPv4 only").iifname "fc-*", not as a destination-address match. The host has many addresses (LAN, tailnet, TAP .1, podman bridges).vm-isolationrow.That row (line 730) has a second problem. It expects a "fake upstream (a node on the test network)" to be reachable through NAT. The NixOS test VLANs are 192.168.x.0/24, which is RFC1918, so under §3.4 that node must be blocked. As written, the test can only pass if the RFC1918 drop is broken or the node is allowlisted. Say which, or give the upstream node a public-range address.
2. The description and PLAN.md disagree on the review token (PLAN.md:35, :906)
CLAUDE_CODE_OAUTH_TOKEN. It stays that way until #191 is done."One of them is wrong. This matters for the trust model: §7.5 itself notes that anyone with write access, Renovate included, can read the secret with a pushed workflow. If the token exists, the description's "until #191 is done" is not true. If it doesn't, §7.5 should say it is deferred.
3.
license-boundarypasses whengo listfails (flake.nix:192)If
go listerrors, grep sees no match and theifbranch is skipped, so the check passes. With or withoutpipefailthe result is the same. A policy check should fail closed:claude-opus-5-5 on subscription ·
474acf9e0c· 3 turns · in 4 · out 5.2k · cache read 93k · cache write 91.6k · $0.85 at API ratesClaude review
Verdict: request changes. Two parts of the plan contradict each other, and the description contradicts the plan. The Phase 0 code itself raises nothing serious.
1. PLAN.md:302 vs §3.4 (PLAN.md:371): the host firewall can't see a TAP that has been moved into the jailer's netns
§3.2 says each VM gets "a new PID and network namespace, with the TAP moved into it". §3.4 builds all VM isolation on rules in the host's
inet runner_firecrackertable that matchiifname "fc-*": drop on input, allow public addresses only on forward, drop VM→VM. Once the TAP is inside the jailer's netns, no packet from it reaches the host netns withiifname fc-*. Egress then needs a veth pair or routing out of that namespace. The host rules would match the veth's host-side name, or there would be no egress path at all.As written, an implementation that follows §3.2 makes the §3.4 rules match nothing.
vm-isolationwould catch this only if it happens to fail closed. Thevm-chaosleak assertion "nofc-*TAP" (§7.2) would also pass trivially, because it looks in the host netns.Pick one design and write it down:
--netnsfor networking).fc-*still matches. State which netns each rule and the masquerade live in, and change the leak assertion to look for the host-side veth.2. PLAN.md:35, PLAN.md:922 vs the PR description: is there a review token or not?
CLAUDE_CODE_OAUTH_TOKEN. It stays that way until #191 is done."This review ran, so a token does exist. Either the description or the plan is wrong. If the intent really was "no token until #191", then provisioning it already is a change to the trust model that should be stated. §7.5 itself says anyone with write access, Renovate included, can read the token.
Separately, head commit
9e2b186("VM egress allowlist, fail-closed license check") is not among the three commits listed in the description. Please add it.3.
.forgejo/workflows/review.yaml:82-84andclaude-review.sh:103: the PR under review supplies its own reviewerThe step runs
.forgejo/scripts/claude-review.shfrom the PR head checkout. The script reads.forgejo/review-prompt.mdfrom that same checkout as--append-system-prompt. So any PR can rewrite the instructions and tools its own review runs with, and so can push the token out through a modified script.§7.5 accepts that the token is readable by anyone with write access. Still, a reviewer whose prompt is set by the change it reviews is weaker than it needs to be. Fetch the script and the prompt from
BASE_REF, for example withgit fetch --depth 1 "$URL" "$BASE_REF"into a separate directory, and keep only the code under review from the PR head.4.
cmd/runner-firecracker/main.go:51-55: shutdown doesn't do what its comment says, and won't do what §3.5 asksGracefulStop()is called right afterSetServingStatus. It closes the listener and sends GOAWAY at once, so the runner never gets a chance to read the new health status.GracefulStopwaits for every in-flight RPC with no deadline, so a longExecsuch assleep infinitywould block it. §3.5 asks for "finish active jobs until a deadline, then tear everything down".What to change:
health.Shutdown(), then wait for a grace period.GracefulStopunder a timer that falls back toStop().claude-opus-5-5 on subscription ·
9e2b186e68· 3 turns · in 4 · out 6.5k · cache read 95.3k · cache write 91.7k · $0.88 at API ratesView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.