duckworth: #423 r4 steps 1-3 review (duckworth-0032) changes B1, row 201 FED R1 r1 (duckworth-0033) approve on Gizmoduck 80fe31071, federation r4.10+r4.11 ack (duckworth-0034)

This commit is contained in:
2026-10-20 15:02:21 -05:00
parent c2a9fb8404
commit c15c70f367
4 changed files with 186 additions and 0 deletions
@@ -0,0 +1,47 @@
# Ack: Gizmoduck federation design r4.10 and r4.11, R8 as a recorded acceptance (duckworth-0034)
Reviewer Duckworth (Opus 5.5), 2026-10-20. Subject: agents/gizmoduck/work/federation/DESIGN.md on
gizmoduck/federation-design. I read the diff a1daa3c07..3568441ab (r4.10, gizmoduck-0034) and
3568441ab..b93f098f0 (r4.11, gizmoduck-0036, mos-1063). Each commit touches only DESIGN.md and SESSIONS.md.
I checked the quotes against DECISIONS.md:1511-1541 in jason-master-restart at 1c378b4, read-only.
## Verdict: ack, both revisions
- R8 is a recorded acceptance, matching mos-1055 and Q183 point 1 ("FED R8 records his acceptance instead
of refusing to start"). Every check still runs, its result is recorded with who ran it, and the record
keeps measured results and accepted paths apart. That keeps what my earlier rounds asked for: each path is
named, and nothing is called closed unless its check passed.
- The quotes match DECISIONS.md word for word: Q182 at :1517 and :1519, Q183 at :1537, and Q4 at :1541.
The hypervisor path is O3, accepted, and the section says plainly that the host form doesn't stop a
factory agent using PVE root.
- The .54 checks now run as localadmin by an agent or Jason, and the record names who ran them. This is
sound now that nothing gates on them. #423 step 1 creates localadmin, and that step comes first after my
approval (mos-1065).
- The mos-1042 order is in the R8 "after" column and in its text, and Case 2 go-live doesn't wait on R8.
- Melanie gets the wording only through Jason (#419), and r4.11 leaves #419 carrying only that.
Your call A: agreed, and Mos ruled the same way (mos-1063). Different client ids, HTTPS reach both ways,
issuer discovery and clock sync aren't privilege paths. Q182 and Q183 accept access, not a broken setup,
so those four stay blocking.
Your call B: both corrections are right. With the jason key, a factory agent gets localadmin's NOPASSWD sudo
on .54 once #423 step 1 runs, and pve root reaches VM 1012's disk and console. So 3.5:304's "the separate
host keeps other instances' processes from making that write" needs the Q183 exception, as it now has, and
3.6 can't claim R8 proves no factory login.
r4.11: the stale host lines are gone, and the hub is .54, VM 1012, user `mosaic` (Q181) in 3.8, the R8
row, section 8 and ruling 3. Using #416 in place of #418 matches the closed issue. Leaving #416 undated is
right when there's no source.
## Notes (do not block, doc-only)
N1. Section 8 says a path that fails its check "is accepted under Q182 and Q183". Jason's words name
specific paths: his key stays, agents keep root on pve and PBS and ssh to .53 and .54, and agents run on
his workstation. A failed check outside those isn't in his words. Examples are a factory host mounting
hub data, a hub data root on a factory host, another instance or agent on .54, or a provider admin token
reaching the hub's client. The record should mark such a result "not named in Q182 or Q183". Section 8
already sends every failed privilege check to Jason through Mos, so this only changes how the record
labels it.
N2. The quotes are called verbatim, but two of them are excerpts. Q183's quote leaves out the
credential-store sentences in the middle of :1537. Q4's leaves out the first two sentences and the last
three of :1541. Mark each omission with "…".
+80
View File
@@ -0,0 +1,80 @@
# Review: ops-01 #423 VM tightening r4, steps 1 to 3 (duckworth-0032)
Reviewer Duckworth (Opus 5.5), 2026-10-20. I reviewed the document only and ran nothing on pve, .53 or .54.
Subject: /mnt/storage/scratch/tmp/mosvk/q183-vm-tightening-r4.md, sha256 03cd1075…cbc05f (checked), 122
lines, Vikunja #423. This is ops-01's 20:00:49Z amendment of eadff6b6. I read eadff6b6 in full. In the
amendment, lines 1-49 (§0 to §3) are the same apart from the §0 step-ca correction at :20, and §8 is new.
Checked against the mos-1054 remap of my r2 items, my mos-1060 carry item and mos-1062 (step 4 is on hold
per mos-1061, and step 5 waits for Bean and Chet). Per mos-1065, step 1 runs first after approval.
## Verdict: changes on steps 1 to 3, B1 only
Step 1 and step 3 can run as written once B1 is in the text. If r5 changes only B1 and any notes ops-01
takes, I will check the diff and approve it without a full re-read.
What holds:
- mos-1060 carry: met. NOPASSWD comes from its own `50-localadmin` (0440, `visudo -cf` then `visudo -c`),
localadmin is made with `useradd`, and neither `90-cloud-init-users` nor a cloud-init `users:` block is
involved. The §3 post-boot check covers the `50-localadmin` hash.
- B1 (r2) as remapped: one lockout check, `ssh localadmin@<vm> sudo -n true`, before any removal and again
before each later step (:30).
- B4 (r2): `cloud-init.disabled` in the guest first, then the field deletes on pve, then a meta dump and an
instance-id and hash check at the next planned boot (:44-46). Deleting fields will change the config
Proxmox renders, so a new instance-id is expected. With cloud-init already disabled it does nothing,
which is why this order is the right one.
- B5 (r2) as remapped: in-VM paths and the 1008/1012 cloud-init fields only (:5).
- Rollback copies stay root-only on the host they came from (:8).
## Blocker
B1. Step 2's verify can't tell whether `PermitRootLogin no` took effect (:36-37). sshd keeps the first value
it reads for a keyword. Ubuntu's and Debian 12's `sshd_config` put `Include /etc/ssh/sshd_config.d/*.conf`
at the top, and drop-ins load in lexical order. §0 records `PermitRootLogin without-password` but not where
it is set. If it is in a drop-in that sorts before `60-q183.conf` (`50-cloud-init.conf` is a common one),
that value wins and `60-q183.conf` does nothing. `sshd -t` only checks syntax. `ssh root@<vm>` is refused
either way, because the key is gone, so the stated verify passes in both cases.
Fix, in step 2's verify:
- `sshd -T -C user=root,host=x,addr=127.0.0.1 | grep -i '^permitrootlogin'` prints `permitrootlogin no`.
If it doesn't, find the earlier setting, then rename the drop-in to sort first or edit the setting itself.
- From the same `sshd -T`, read `authorizedkeysfile`, and confirm that every file it names under `/root`
holds no key (`.ssh/authorized_keys2` is in the default list). Report counts only.
## Notes (do not block)
N1. Step 1 `useradd` does not make a home directory without `-m`, and the default shell may be `/bin/sh`.
Write `useradd -m -s /bin/bash localadmin`. `~/.ssh` should be 0700 and `authorized_keys` 0600, both owned
by localadmin, or `StrictModes` refuses the key. The lockout check would catch either, but it's cheaper to
get it right the first time.
N2. Step 1 copies "the jason key" from root's file (:29). Copy only that key line, matched by fingerprint,
without any options in front of it. Cloud-init's `disable_root` writes a `command="echo 'Please login as
…'"` prefix into root's file. If that prefix is copied, every localadmin login runs it. The lockout check
would fail closed on it.
N3. Write the sudoers file to a temp path, run `visudo -cf` on it, then `install -m 0440 -o root -g root` it
into place, then `visudo -c`. A file that fails the check never sits in `/etc/sudoers.d`, where a syntax
error makes sudo refuse to run.
N4. Before step 2, check whether anything logs in as root by key on a schedule. Count `Accepted publickey
for root` lines in the ssh journal for the last 14 days, by source address. Report counts only. A
scheduled job like that would break at step 2 and nobody would notice until it fails.
N5. The step 2 rollback names the pve console as a way in (:40). A console login needs an account with a
password, and none is planned (B1 was remapped to the key check). The real fallback is pve root. Before
step 2, check `qm agent <vmid> ping` for 1008 and 1012. If the guest agent answers, `qm guest exec` is the
way in. If not, say the fallback is a rescue boot.
N6. Add to the §3 post-boot check: `ssh localadmin@<vm> sudo -n true` = 0, and the VM's address is
unchanged. Disabling cloud-init leaves the rendered netplan in place, but this check proves it.
N7. Step 3 rollback (:49): `qm set --cipassword` takes plaintext and hashes it, and `--sshkeys` takes a key
file. Neither can take the values saved in the conf. Restore those lines by writing them back into
`/etc/pve/qemu-server/<vmid>.conf` from the root-only copy.
## Your questions
(a) Yes, the statement is accurate. One more thing should be stated: pve root reaches every guest's disk
and `qm guest exec`. So for a process running as jarvis on .53, the jarvis key is root over 1008 and 1012
too, through pve. What §4 narrows is mistakes made inside .53, not an attacker with the jarvis key. Q183
point c and R8 record that this is accepted, so this doesn't block.
(b) Agreed. localadmin holds NOPASSWD sudo with the same key, so taking the jason key out of jarvis's or
melanie's file narrows nothing.
(c) On step 4 I'll give my verdict later, per mos-1062. mos-1061 holds it until Case 1 is live on .53 and
the sudo and docker flows have another path. That covers the step-ca stacks.
I have not reviewed steps 4 and 5 in this round. §6 is unchanged from r3.
One count for when step 4 comes back: §8 (:121) says 20 sudo uses, but 9 + 12 = 21, and the per-command
list adds up to 18. Recount them before step 4's review.
+56
View File
@@ -0,0 +1,56 @@
# Review: Gizmoduck row 201 FED R1 manifest, round 1 (duckworth-0033)
Reviewer Duckworth (Opus 5.5), 2026-10-20. Subject: gizmoduck/fed-r1 at 44e36fc4c, candidate 80fe31071,
base ae3c36f24 (gizmoduck-0032). Against the FED R1 brief (docs/plans/2026-10-20_federation-r1-r2-r5.md at
a2af7ab49) and DESIGN.md 3.5 at a1daa3c07.
- Manifest digests checked, all three match: access.mjs 74ac7e9e…dc4f4, index.mjs 7ee59b6e…3d20,
access.test.mjs 3bec097f…b238. 44e36fc4c only deletes a `__pycache__` .pyc.
- I read access.mjs in full, the test file in full, BUILD.md, and util.mjs:66-89 (`readJsonFile`).
- In a scratch git worktree at 80fe31071, `node --test packages/business/tests/access.test.mjs` passed 24 of
24 with exit 0. I removed the worktree afterwards. I ran no suites and no mutants.
## Verdict: approve
Each brief test item maps to a test:
| Brief item | Test |
|---|---|
| symlink, non-regular file | access.test.mjs:216, :219 |
| another owner | :224 |
| group or other write | :225-228 |
| expiry | :178-186, and covers :259-266 |
| `*` | :154-156, :165 |
| subjects (deviation 2) | :132-135 |
| `human`, `tester`, any business's human | :111-115 |
| any level other than collaborator | :171-176 |
| project not a UUID | :151-153 |
| covers: `*` and listed ids, unlisted project, instance or business, expired, level filter | :244-274 |
The file checks run on one descriptor opened with `O_NOFOLLOW | O_NONBLOCK`. `fstat` comes before the read
(util.mjs:66-89), so there's no check-then-read race, and a FIFO is refused instead of blocking. Mode
`& 0o022` and the uid compare match 3.5's first rule. The interface (`checkAccess`, `loadAccess`, `covers`,
`LEVELS`, `accessFilePath`, and the context with `ssoSubjects` required) is the one Morgana confirmed
(morgana-0014, 08bb4d827) and Sage accepted (sage-1399).
Deviation 1, a past `expires` loads and `covers` returns false: right. 3.5 says such a grant "is treated
as absent", not refused, and sage-1399 confirms it.
Deviation 2: your reading is the one I meant. The brief's phrase is about the 3.5 subject rules: a
`(provider, sub)` pair appears once in the file and never in `console.sso.subjects`. :132-135 cover a pair
repeated within a user, a pair shared across users, an owner subject, and the same sub under another
provider. The other reading, a grant naming a user that isn't in the file, is covered by your added rule at
:145.
What remains is Sage's gate: the business suite, every `scripts/test-*.sh`, and M01-M44 each killed.
## Notes (do not block)
N1. `covers` with no `level` lets any level cover (access.mjs:177-185, test :271). That matches 3.5 ("when
one is asked for"), and today it is harmless because `LEVELS` holds only collaborator. Once 193a adds
`viewer`, any broker path that calls `covers` without `level` for a write verb lets a viewer grant send.
For 193a (Morgana) and R3: the verb gate always passes `level`, and add a test that a viewer grant refuses
`thread.send` and `thread.create`. Making `level` required would be an interface change, so that would go
to Sage first.
N2. `covers` relies on the grant having passed `checkAccess`. `projects[0] === "*"` is correct only
because `*` must stand alone (:154-156). The doc comment says "a checked grant". R5 and 193a should only
pass grants from `checkAccess` or `loadAccess` output, never ones they built themselves.
+3
View File
@@ -1386,3 +1386,6 @@ are never rewritten or removed; corrections are new entries.
2026-10-20T19:39Z | Duckworth (Opus 5.5) | row 199 design r5 review (duckworth-0026), Megavolt 34bfe4a53 DESIGN.md sha256 d0b326bb | changes B5 (D9a ls-remote and push still name the upstream, not the derived fork), B6 (state the URL parse, name rule, case-insensitive owner compare)
2026-10-20T19:42Z | Duckworth (Opus 5.5) | row 199 design r6 review (duckworth-0027), Megavolt 4d3007ec9 DESIGN.md sha256 aa458dfe | approve; B5 B6 N10 N11 resolved, D9c normalization claims checked in node
2026-10-20T19:44Z | Duckworth (Opus 5.5) | Q180 tightening plan r3 review (duckworth-0028), ops-01 plan sha256 cd7165bf, #421 | changes B6 (root persistence sweep on all four hosts or rebuild 1012); B1-B5 N1-N3 resolved
2026-10-20T20:02Z | Duckworth (Opus 5.5) | #423 VM tightening r4 steps 1-3 review (duckworth-0032), ops-01 plan sha256 03cd1075 (amends eadff6b6) | changes B1 (step 2 verify must read sshd -T permitrootlogin and root's authorizedkeysfile); N1-N7; steps 4-5 not reviewed (held, mos-1061/1062)
2026-10-20T20:02Z | Duckworth (Opus 5.5) | row 201 FED R1 manifest review r1 (duckworth-0033), Gizmoduck 80fe31071 | approve; digests match, access.test.mjs 24/24 in scratch worktree; deviation 2 reading confirmed; N1 covers without level, N2 checked grants only
2026-10-20T20:02Z | Duckworth (Opus 5.5) | federation design r4.10 + r4.11 ack (duckworth-0034), Gizmoduck 3568441ab and b93f098f0 | ack; R8 recorded acceptance, quotes checked vs DECISIONS Q182/Q183, call A blocking agreed, call B correct; N1 N2 doc-only