Design inputs: SPIKE-universal-iso-{1,2,3,4}-2026-07-31.md. Every choice below is a measurement.
NEW: scripts/iso/pkg/ — the felhom-bootstrap .deb, built from committed source.
Two files only (script + unit), NOT three: felhom-bootstrap.sh:91 reads /etc/felhom/bootstrap.env
only 'if [[ -r ]]', and its defaults at :95-96 are EXACTLY what the pairing env set
(build-felhom-iso.sh:257-258) — so shipping it would add a 0600 file to a public package to express
values the script already defaults to. NO dependencies: the binaries it calls run at FIRST BOOT,
not at postinst time, so SPIKE 4's open 'dpkg --configure -a' ordering question does not arise.
The postinst is structurally incapable of failing (no 'set -e', every statement guarded, ends
'exit 0'); build-deb.sh self-asserts G8/G9 and REFUSES to emit a package that violates them.
iso-repack.sh — two changes, both narrowing rather than deleting:
- R-155 guard: now applies to FELHOM_MENU=single ONLY. It protected the single-entry mode's promise
(one button labelled 'install' must not drop into a disk-picker); a release image carries no
auto-installer-mode.toml BY DESIGN (gate G1), so refusing it would be the guard firing on the
shape it describes rather than the one it prevents.
- the menu collapse now has a release mode: two INTERACTIVE entries, Graphical default, timeout 15.
Entry-count and banned-token gates are per-mode; the six-token list is UNCHANGED for single mode.
- .deb injection into /proxmox/packages/, with a skip-list collision check (a colliding name would
be dropped silently — the inert-payload class) and a post-remaster assertion that it landed in
final.iso, not merely in the extract tree.
build-felhom-iso.sh — --release: no profile, no root hash, no answer.toml, no prepare-iso at all.
Skipping prepare-iso is what removes the Automated entry by construction, since the stock grub.cfg
emits it only inside 'if [ -f auto-installer-mode.toml ]'.
R-128 RULING — FIXED, by correcting the claim rather than inventing an assertion for it. The comment
said ISO_VERSION 'aligns with SCRIPT_VERSION'; nothing evaluated it and the two had drifted. The
coupling does not exist: the ISO is frozen, felhom-host-install.sh is fetched at run time from main
(R-94/R-110), so an assertion would invent a constraint. Comment corrected, ISO_VERSION -> 1.26.0.
Release gate G6 AMENDED before the build, with its reasoning recorded in the runbook: the six-token
ban existed to keep users away from the manual installer, which the ruling makes the product.
'proxtui' (the TUI installer we ship) and 'nomodeset' (its graphics fallback) are dropped for
release images; proxdebug/Rescue Boot/memtest/fwsetup stay banned in both modes.
9.4 KiB
The public ISO release gate
Written before the first release image was built (2026-07-31), deliberately: a standard defined in advance cannot be rationalised afterwards, and this is the artifact that most needs one — once a file is on
iso.felhom.euand someone has downloaded it, it cannot be recalled.Design inputs:
documentation/audits/SPIKE-universal-iso-{1,2,3,4}-2026-07-31.md. Every criterion below exists because one of those spikes measured why.
How to use it
Run every check against the exact file that will be uploaded — not against build inputs, not against a sibling built the same way. A build is reproducible in theory and the uploaded bytes are what a stranger receives.
An absent finding is not a finding. Each check records what was scanned for, so "no hits" means "this pattern was searched and did not appear", not "nothing occurred to me".
Any FAIL stops the publication. Stopping is a good outcome.
The criteria
Let $ISO be the file about to be uploaded.
G1 — No answer.toml
osirrox -indev "$ISO" -find / -maxdepth 1 2>/dev/null | grep -cE "'/answer\.toml'|'/auto-installer-mode\.toml'"
PASS = 0.
Why: the answer file is the only secret-bearing artefact a Felhom ISO has ever carried (Spike 1 §6,
by enumeration against the stock ISO). Removing it deletes the baked root hash, the disk profile, and
the entire Spike 1–2 problem space in one move, and turns the most dangerous property of the image
into a one-line assertion. Without auto-installer-mode.toml the stock grub.cfg does not emit the
Automated entry at all (Spike 1 §5), so the unsafe path is absent by construction rather than guarded.
G2 — No root password or hash
osirrox -indev "$ISO" -extract /answer.toml - 2>/dev/null # must not exist (G1)
# and, across every file the pipeline adds:
grep -c 'root-password\|root-password-hashed\|\$6\$\|\$y\$\|\$2[aby]\$'
PASS = 0 occurrences, and no *.rootpw.txt emitted beside the output.
Why: build-felhom-iso.sh:270-283 mints a root hash unconditionally in the answer-file modes, and
Spike 1 proved by extraction that it ships inside /answer.toml where any downloader can read it. On
a published image that is one credential shared by every box installed from it.
G3 — No SSH key baked
grep -c 'root-ssh-keys\|ssh-rsa\|ssh-ed25519\|ssh-dss'
PASS = 0 across the pipeline-added files.
Why: FELHOM_ROOT_SSH_KEY is one uncommented profile line away from shipping a shared key to every
downloader, and Spike 1 §4.6 found exactly that had already happened on demo-felhom — from an
uncommitted profile. Same shared-credential class as G2.
G4 — No customer identity
grep -c 'FELHOM_CUSTOMER_ID=[^"]\|FELHOM_RETRIEVAL_PASSPHRASE=[^"]\|claim[_-]code\|api[_-]key\|Bearer '
PASS = 0 with a value. Empty variable declarations inside felhom-bootstrap.sh are expected
and are not hits (Spike 1 §6 established this distinction — :89 initialises them empty).
G5 — No credential of any kind, by enumeration
Diff the file list against the stock PVE ISO and inspect every added path:
osirrox -indev <stock-pve.iso> -find / | sort > /tmp/stock.txt
osirrox -indev "$ISO" -find / | sort > /tmp/rel.txt
comm -13 /tmp/stock.txt /tmp/rel.txt # everything the pipeline added
PASS = every added path is accounted for and none carries a secret. Scan the content-bearing ones
for: PEM private-key headers, ssh-rsa/ssh-ed25519 material, Bearer tokens, api_key=,
passphrase=, password=, token= with ≥12-char values, and crypt hashes ($6$, $y$, $2[aby]$).
Why enumeration and not a pattern sweep: a grep only finds what it was told to look for. Spike 1 §6
found /answer.toml this way precisely because the earlier recon had grepped the wrong file.
G6 — The boot menu is present, with both paths and a human timeout
osirrox -indev "$ISO" -extract /boot/grub/grub.cfg /tmp/g.cfg
grep -c '^[[:space:]]*menuentry ' /tmp/g.cfg # >= 2
grep -E '^set (default|timeout|timeout_style)=' /tmp/g.cfg
PASS = at least one interactive entry present; set default= points at an interactive entry;
set timeout ≥ 10; and timeout_style uses the underscore spelling.
Why the timeout: Spike 2 lost an entire probe to a 1-second menu. A human choosing between two
install paths on unfamiliar hardware needs to read them first. Why the spelling: the stock PVE config
writes timeout-style, which GRUB does not recognise as a variable name at all (Spike 2 §5.2) —
Felhom's underscore form is the one that works.
Also assert the safety property still holds:
grep -v '^[[:space:]]*#' /tmp/g.cfg | grep -cE 'proxdebug|Rescue Boot|memtest|fwsetup'
PASS = 0.
Amendment, 2026-07-31, before the first build — recorded rather than made quietly. This criterion was first written as the six-token list
iso-repack.sh:160-164enforces (proxtui|proxdebug|nomodeset|Rescue Boot|memtest|fwsetup), on the stated rationale "no live route to a manual disk-picker". That rationale is obsolete for a public image and was already obsolete when it was written. The operator's ruling makes the manual installer the product: the person installing chooses their own disk in the stock installer, which shows the target and aBootdisk(s)summary before erasing. A token list whose purpose is to keep users away from the manual installer cannot be applied to an image whose purpose is to offer it.Two tokens are therefore dropped for the release image only:
proxtui— it is the Terminal UI installer, one of the two entries the image deliberately ships; andnomodeset— a graphics fallback for the same installer, needed on hardware whose GPU the default mode cannot drive.Four are kept, and they are the ones that were ever the real hazard:
proxdebug(drops to a shell),Rescue Boot(boots an existing system rather than installing),memtestandfwsetup(not installers at all). The original six-token gate remains unchanged for the single-entry appliance mode, where its original rationale still holds exactly.
G7 — The Felhom package is present, at a recorded version
osirrox -indev "$ISO" -find /proxmox/packages 2>/dev/null | grep -c 'felhom-'
osirrox -indev "$ISO" -extract /proxmox/packages/<pkg>.deb /tmp/p.deb
dpkg-deb -I /tmp/p.deb | grep -E '^ (Package|Version):'
PASS = exactly one felhom-*.deb, its version recorded in the manifest, and its sha256 matching
the package built from committed source.
Why: Spike 4 measured that this is the only delivery mechanism that survives an interactive install
([first-boot] is never placed on the system — Config.pm:118, Install.pm:746, :1360).
G8 — The postinst cannot fail, and cannot need what it hasn't got
dpkg-deb --ctrl-tarfile /tmp/p.deb | tar -xO ./postinst > /tmp/postinst
grep -cE 'systemctl (start|daemon-reload|restart)' /tmp/postinst # 0
grep -cE '\b(curl|wget|apt-get|apt|nc|ping)\b' /tmp/postinst # 0
grep -c 'set -e' /tmp/postinst # 0
tail -1 /tmp/postinst # exit 0
PASS = all four.
Why, all four measured in Spike 4 §3: pid1 in the installer chroot is unconfigured.sh and no
systemd is running, so start/daemon-reload are meaningless; the network was up only because the
installer's DHCP happened to hold, so a box installed with the cable out has none; and a postinst that
exits non-zero surfaces as an error in the middle of a customer's install — far worse than the stub
not running. set -e is banned because it converts any unexpected non-zero into exactly that failure.
G9 — felhom-bootstrap.sh matches repo HEAD byte-for-byte
Extract the script from the package and compare:
dpkg-deb --fsys-tarfile /tmp/p.deb | tar -xO ./usr/local/sbin/felhom-bootstrap.sh | sha256sum
sha256sum scripts/iso/felhom-bootstrap.sh
PASS = identical.
Why: it is the one genuinely frozen, drift-capable payload — everything else the box uses is fetched
at run time from main. An ISO in a drawer for six months still runs this exact script.
G10 — The profile and every build input are committed
git status --porcelain scripts/iso/ # empty
git rev-parse HEAD; git rev-parse origin/main # equal
PASS = clean and pushed, and the manifest records the commit.
Why: Spike 1 found demo-felhom was installed from an uncommitted profile, so one of the two
reference boxes cannot be rebuilt from main. A published image must never reach that state, and
R-144 records a lab ISO that already has (nested-probe's profile no longer exists).
G11 — A published checksum, and a verified round trip
PASS = the .sha256 and the manifest are uploaded beside the ISO, and the file downloaded from
https://iso.felhom.eu/<name> checksums to the same value. The round trip is the claim — a local
checksum proves only that the local file is what it is.
G12 — The bucket stays private
PASS = the bucket's Public Access remains Disabled; the custom domain is the only public path.
Result recording
Record each criterion as PASS/FAIL with the observed value and what was scanned for, in the release report. A criterion with no recorded observation is a criterion that was not run.