Files
felhom.eu/REPORT-universal-iso-spike.md
T
admin ea00976403 SPIKE: a universal ISO needs a different disk strategy and a locked root
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.
2026-07-31 12:12:35 +02:00

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.