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

5.7 KiB

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