ea00976403
Findings only — no script, profile or build file changed; no ISO built, nothing published. documentation/audits/SPIKE-universal-iso-2026-07-31.md - R-139 (HIGH): a disk filter matching >1 device does NOT fail safe. Observed in a nested VM — the installer silently picked one of two matching disks and wiped it; validate-answer accepts such an answer. The 'filter did not match any devices' guard covers the ZERO-match case only. - No udev property distinguishes an internal system disk from external media. Measured on demo-felhom with its 1TB external attached: ID_BUS='ata' for BOTH, lsblk RM=0 for both, and device-info exposes no removability property. demo-hp's NVMe carries no ID_BUS/ID_TYPE at all. - R-141 (HIGH): the answer schema makes a root credential mandatory, but root-password-hashed='*' validates AND installs to completion. [first-boot].ordering accepts 'before-network', the only ordering that closes the exposure window structurally. - Q3: prepare-iso leaves grub.cfg byte-identical to stock (15 entries, automated AND interactive) — a two-entry menu is purely a Felhom grub.cfg.tmpl change. - R-129 resolved: demo-hp's key is the operator's own, added post-install; demo-felhom's IS baked by an uncommitted profile. The reachable-before-rotation measurement FAILED twice and is recorded as failed, not inferred. Opens R-139..R-147; restates R-128.
94 lines
5.7 KiB
Markdown
94 lines
5.7 KiB
Markdown
# REPORT — SPIKE: what does a genuinely universal ISO require? (2026-07-31)
|
|
|
|
> Written as **`REPORT-universal-iso-spike.md`**, not `REPORT.md`: the shared file was overwritten at
|
|
> 09:24 today by the hub v0.85.0 Network-card session, and the standing rule is that the second
|
|
> session in a shared clone never touches it.
|
|
|
|
**Class: Spike.** Output is a findings document. **No production code was changed, no ISO was built,
|
|
nothing was published.** Full evidence:
|
|
`documentation/audits/SPIKE-universal-iso-2026-07-31.md`.
|
|
|
|
## Why this ran
|
|
|
|
The ISO build/publish task stopped before its Part 2. Its Part 0 found that every ISO bakes a root
|
|
password hash into `/answer.toml`, and that `generic.profile` picks a disk by the name `sda` rather
|
|
than by identity — both of which make a public download a product decision. This spike establishes
|
|
what a genuinely universal image would require.
|
|
|
|
## What was answered
|
|
|
|
**Q1 — disk selection. The central finding, and it is worse than the known hazard.**
|
|
|
|
A filter that matches more than one device **does not fail safe**. Observed in a nested VM with two
|
|
identical disks and `filter.ID_SERIAL_SHORT = "SPIKE*"`: the installer silently selected one and wiped
|
|
it (`disk0` hash `de001102…` → `382b55ca…`; `disk1` byte-identical throughout). `validate-answer`
|
|
accepts the answer — it cannot know what the filter resolves to on the target. The
|
|
`filter did not match any devices` guard the safety profiles rely on covers the **zero**-match case
|
|
only.
|
|
|
|
And a filter that reliably matches exactly one disk on unseen hardware cannot be written, because
|
|
**no property distinguishes an internal system disk from external media.** Measured on `demo-felhom`
|
|
with its 1 TB external drive attached — the exact hazard configuration:
|
|
|
|
- `ID_BUS` is `ata` for **both** (the external unit is a SATA drive in a USB caddy)
|
|
- `lsblk RM` is `0` for **both**; `device-info` exposes no removability property at all
|
|
- on `demo-hp`, the NVMe carries **no `ID_BUS` and no `ID_TYPE`** — a different property set entirely
|
|
- nothing anywhere expresses *role*: on `demo-hp` the system disk is the small SATA one and the big
|
|
NVMe is customer data
|
|
|
|
**Q2 — root password.** A credential field is mandatory (`One of global.root-password or
|
|
global.root-password-hashed must be set`), but a *usable* one is not: `root-password-hashed = "*"`
|
|
validates **and installs to completion** (guest powered itself off after 200 s using
|
|
`reboot-mode = "power-off"` as the completion signal). `[first-boot].ordering` also accepts
|
|
`before-network` — the only ordering that could close the exposure window structurally, since today's
|
|
bootstrap unit is `After=network-online.target pveproxy.service` and the pairing poll waits
|
|
indefinitely for the operator's bind.
|
|
|
|
**Q3 — two-entry boot menu: yes, trivially.** `prepare-iso` leaves `grub.cfg` **byte-identical** to
|
|
stock; a prepared ISO has 15 entries including automated *and* interactive. Felhom's repack is what
|
|
collapses it to one. No PVE-side obstacle exists.
|
|
|
|
**Q4 — what else is baked.** By enumeration against the stock ISO, not pattern-guessing: the pipeline
|
|
adds exactly six paths; both OS squashfs payloads are byte-identical to stock; all three first-boot
|
|
payloads match repo HEAD or carry only the hub URL. `/answer.toml` is the only secret-bearing content.
|
|
|
|
**R-129 resolved.** demo-hp's key is the operator's own, added post-install — not baked. But
|
|
demo-felhom's *is* baked, by an uncommitted profile, so the "no baked key" claim was already false for
|
|
one demo box and nothing recorded it.
|
|
|
|
## What failed, and is recorded as failed
|
|
|
|
**The reachable-before-rotation measurement was attempted twice and both attempts were invalid.**
|
|
Attempt 1 reported a plausible **`0 s`** that was an artefact — QEMU's `hostfwd` accepts on the host
|
|
before touching the guest, and the same run then failed to read an SSH banner at all. Attempt 2, with
|
|
a protocol-level detector, reported `never` because the install VM had no NIC, so the installed
|
|
system's interface config could not match the boot VM's. **No number is reported.** The structural
|
|
finding (unit ordering + unbounded pairing poll) stands instead and is the stronger claim.
|
|
|
|
Also not established: that the locked root account is actually locked (install completion is a
|
|
different claim), and whether GRUB single-user recovery works — the latter gates the escrow-vs-discard
|
|
ruling and should be measured before that ruling is made.
|
|
|
|
## Asked of the operator
|
|
|
|
One decision, framed in §4.5 of the findings doc: **escrow or discard** a per-box root password. Not
|
|
recommended either way — the D6 trust model argues against a hub-held fleet-wide vault, and an unbound
|
|
box has no agent access, which is exactly when console access matters.
|
|
|
|
## R-rows
|
|
|
|
**R-139** (HIGH, multi-match wipes silently) · **R-140** (`device-match` exits 0 on no-match) ·
|
|
**R-141** (HIGH, mandatory + fleet-shared credential, unbounded window) · **R-142** (`FELHOM_ROOT_SSH_KEY`
|
|
can ship a shared key, no assertion) · **R-143** (retired `kisfenyo@windows` key still live on
|
|
demo-felhom) · **R-144** (`nested-probe` ISO unreproducible — profile gone) · **R-145** (variant naming
|
|
undocumented) · **R-146** (`root-password-hashed = ""` validates) · **R-147** (hash substituted into
|
|
the answer template's own comments) · **R-128** restated (a comment, not an assertion).
|
|
|
|
## Teardown
|
|
|
|
All three layers verified positively: demo-hp scratch removed and `drill-r50`/9201/`storage.cfg`
|
|
untouched, `local-lvm` never written; demo-felhom probe binary removed and its external drive observed
|
|
only; DooPlex scratchpad 3.2 GB → 28 K with the throwaway password `shred`ed. **No hub object was
|
|
created** — confirmed by fetching the customer list and searching it, not by assertion. Nothing
|
|
deleted from `felhom-iso/out/` (17 ISOs intact). Nothing published.
|