Files
felhom.eu/documentation/audits/SPIKE-universal-iso-2-2026-07-31.md
admin 19c932a693 SPIKE 2 complete: locked root closes the PVE web UI; before-network gives a measured zero window
Findings only — no script, profile or build file changed; no ISO built, nothing published.
documentation/audits/SPIKE-universal-iso-2-2026-07-31.md

Both Tier 0 boxes went offline mid-session (provider cable fault; four routes tried, no Tier 2
fallback used) and returned. All three scenarios then ran to completion on real PVE, each signalled
by reboot-mode='power-off' rather than a disk hash.

- A LOCKED ROOT CLOSES THE PVE WEB INTERFACE. Measured at the exact endpoint the UI uses
  (POST /api2/json/access/ticket, root@pam) WITH A WORKING CONTROL: known-password install returns
  HTTP 200 + ticket; locked install returns 401 for every password and none can exist.
  passwd -S root = L, shadow = literal-asterisk, PVE uses the stock PAM stack.
- GRUB recovery mode is also closed ('the root account is locked') — but init=/bin/bash still gives
  an unauthenticated root@(none):/#. A locked box is recoverable, operator-only, at the console.
  The installed GRUB has NO password, so locking root is not a physical-security measure. R-152.
- before-network MEASURED (A/B, same image): the hook RUNS (marker, uptime 6.58s) with entropy 256,
  writable /etc, all binaries and openssl_rand_len=32, while ip_global is EMPTY and
  listen_22_8006 = 0. fully-up is the converse: sshd+pveproxy active, 3 listening. Zero window.
- R-148: answer.toml.tmpl:27 justifies fully-up with a pvesh/pct dependency the stub does not have
  (grep rc=1) — it blocked the ordering now measured as the fix.
- R-149 three ordering values; R-150 Condition-guarded hooks skip silently; R-151 demo-felhom built
  from an uncommitted profile.

Three probes failed and are recorded as failed: a container probe that ran as uid 0, a GRUB probe
that missed the 1-second menu timeout, and a kernel-line edit one line off (caught by a pre-typing
verification screendump). The interim 'Layer 1 teardown INCOMPLETE' is corrected — the fixture had
never landed, because the staging mkdir was in the SSH call that timed out.
2026-07-31 13:57:21 +02:00

28 KiB
Raw Permalink Blame History

SPIKE 2 — closing the last mechanisms, and the universal ISO's shape (2026-07-31)

Class: Spike. Findings only. No production file changed, no ISO built, nothing published.

Continues SPIKE-universal-iso-2026-07-31.md; its results are premises here and are not re-derived.

All four questions are answered, and every answer that mattered is measured on a real PVE install. The session lost its venue mid-run (§0) and resumed when connectivity was restored.


0. Venue loss and recovery — what was tried

At 12:28 CEST both Tier 0 boxes went offline simultaneously. Routes attempted, in order:

# Route Result
1 ping 100.76.96.79 (tailnet direct) 100 % loss
2 ssh demo-hp Connection timed out
3 ssh demo-hp-lan (LAN, ProxyJump felhom-pve) Connection timed out during banner exchange
4 ssh felhom-pve (the jump host) Connection timed out
5 DooPlex tailscale pod Running 6d10h, healthy
6 tailscale status from the pod both peers offline, last seen rising

Far end, not ours. No fallback venue was used — DooPlex is Tier 2, the 2026-07-25 ruling moved drill VMs off it, and this task did not authorise it. An absent fence is not permission.

Connectivity was restored (operator: a provider cable fault nearby) and the boxes returned with uptime 7 days — a pure network outage, no reboot. All probes then ran.

One thing the outage revealed. /mnt/nvme-1tb/spike2/ did not exist when demo-hp came back, and disk usage was unchanged at 6.6 GB: the staging mkdir had been inside the SSH call that timed out as the link degraded, so the fixture never landed and Layer 1 had nothing to clean. The interim report's "Layer 1 teardown INCOMPLETE" was therefore over-cautious rather than wrong — corrected here, and the fixture was rebuilt from scratch.


1. Baselines

Baseline Value
ISO_VERSION 1.25.0scripts/iso/build-felhom-iso.sh:44
SCRIPT_VERSION 1.22.0scripts/felhom-host-install.sh:187
felhom-bootstrap.sh @ HEAD 21bf6a6bde0cb13e3809e2f5c136a49929dcc82eb8d40bbdf6f290a886ee8ab7
PVE base proxmox-ve_9.2-1.iso, 4e88fe416df9b527624a175f24c9aa07c714d3332afb1ee3dbf3879573ef2c6c
felhom.eu HEAD ea00976, clean, == origin/main
proxmox-first-boot pkg 9.2.5 (from the ISO's /proxmox/packages/)
installed guest kernel 7.0.2-6-pve, GRUB 2.12-9+pmx2

Fixture provenance. Probe ISO built on DooPlex from the baseline PVE ISO (prepare-iso --fetch-from partition --partition-label proxmox-ais --on-first-boot diag.sh), sha256 dd97dba5518c70baf4300b119e1042eabf1012d5a046082acf31e60983efaeb9, verified byte-identical at both ends after transfer. Nested VMs ran under plain QEMU/KVM on demo-hp (Tier 0, the designated drill host) under /mnt/nvme-1tb/spike2/ — never local-lvm, no PVE storage registered, drill-r50 and guest 9201 untouched.

Both of Spike 1's fixture defects are corrected in this one: a NIC is present at install time (its absence made the installed system's interface config unmatchable at boot), and completion is signalled by reboot-mode = "power-off" — the guest powering itself off — never by a changed disk hash.

Three scenarios, all installed to completion:

Scenario root credential [first-boot].ordering Install
A root-password-hashed = "*" (locked) fully-up completed, powered off after 191 s
B root-password-hashed = "*" (locked) before-network completed, powered off after 190 s
C a known password (control) fully-up completed, powered off after 191 s

Evidence classes: [MEASURED-PVE] on a real PVE install · [MEASURED-MECH] measured in an isolated container · [STRUCTURAL] read from shipped source or unit files.


2. Q1 — is a locked root actually locked, and what else does it lock out?

2.1 The account's real state — [MEASURED-PVE]

From the first-boot diagnostic on both locked installs (A and B), emitted to ttyS0:

passwd_S_root   : root L 2026-07-31 0 99999 7 -1
shadow_shape    : literal-asterisk
sshd_PermitRoot : yes
pam_common_auth : 1
pve_pam_realm   : 0

The account is genuinely lockedpasswd -S reports L, and the * reached /etc/shadow verbatim. Note sshd_PermitRoot: yes: PVE ships PermitRootLogin yes, so the lock is doing the work, not sshd's configuration.

pve_pam_realm: 0 — there is no PVE-specific file in /etc/pam.d/ matching proxmox|pve, and pam_unix is present in common-auth. PVE uses the stock PAM stack, which is why the §2.2 container measurement transfers to it rather than merely resembling it.

2.2 What can authenticate — [MEASURED-MECH], first attempt invalid

The first probe produced nothing and is recorded as failed: it ran everything as uid 0 so su never invoked pam_unix (rc=0 even with a deliberately wrong password), sulogin died on tcgetattr failed with no TTY, and a sed mangled the control hash. Four cases, zero observations.

Corrected (debian:trixie, util-linux 2.41, non-root caller, real pty via script, chpasswd):

Shadow field passwd -S root su root as a non-root user sulogin (real pty)
real hash (control) P correct pw → SU_OK; wrong pw → Authentication failure
* L Authentication failure (any password) Cannot open access to console, the root account is locked.
! L Authentication failure (any password) Cannot open access to console, the root account is locked.

The control makes it readable: the same harness succeeds with the right password and fails with the wrong one, so the locked rows are the module refusing rather than the harness misfiring. * and ! are equivalent.

On PVE, root SSH was refused on every scenario — but that row is uninformative and is not evidence: the probe supplied a deliberately wrong password in the control scenario too, so it cannot distinguish "locked" from "wrong password". Recorded as a gap, not a result.

2.3 Does the Proxmox web interface still work? — [MEASURED-PVE]. No. It is closed.

The product-level answer, measured against the exact endpoint the web UI logs in through (POST /api2/json/access/ticket, realm root@pam), with a working control:

Scenario Password offered Result
C (control, known password) correct HTTP 200, TICKET ISSUED — login would SUCCEED
C (control) wrong HTTP 401, no ticket
C (control) empty HTTP 401, no ticket
A (locked root) wrong HTTP 401, no ticket
A (locked root) empty HTTP 401, no ticket

The control is what makes this a finding rather than a broken probe: the same endpoint on the same image demonstrably issues a ticket when the password is right. On the locked install no password can produce one, because no password exists.

Therefore a locked root closes the PVE web interface completely — to the customer and to the operator alike. This is a product decision, not a detail: it removes the browser-based route into the box entirely, on top of the console route (§3.2).

Reachability, incidentally measured on all three: SSH banner at +20 s, pveproxy TLS at +2629 s after power-on.

2.4 Does anything else in PVE break? — not investigated

Cron, backup jobs and other consumers of a root credential were not examined. pveproxy itself runs and serves TLS (it answered /api2/json/version), so the daemon is unaffected — only authentication through it is.


3. Q2 — is there a way back into a box whose setup failed?

3.1 Does the installed GRUB expose a recovery entry? — [MEASURED-PVE]. Yes.

The installed system's GRUB is PVE's own, unaffected by Felhom's ISO menu collapse (§4). Screendumped from the locked install:

*Proxmox VE GNU/Linux
 Advanced options for Proxmox VE GNU/Linux
 Memory test (memtest86+x64.bin)
 Memory test (memtest86+x64.bin, serial console)

and inside Advanced options:

*Proxmox VE GNU/Linux, with Linux 7.0.2-6-pve
 Proxmox VE GNU/Linux, with Linux 7.0.2-6-pve (recovery mode)

A recovery entry exists, the menu is editable (e), a GRUB command line is available (c), and there is no GRUB password. Its timeout is 1 second — short enough that a first probe missed it entirely (§3.5).

3.2 sulogin with a locked root — [MEASURED-PVE]. It refuses.

Booting (recovery mode) on the locked install reaches rescue mode and then stops:

Started rescue.service - Rescue Shell.
Reached target rescue.target - Rescue Mode.
You are in rescue mode. After logging in, type "journalctl -xb" …

Cannot open access to console, the root account is locked.
See sulogin(8) man page for more details.

Press Enter to continue.

The advertised recovery path is closed on a locked-root box — verbatim the container result, now confirmed on real PVE. systemd invokes sulogin for both rescue.target and emergency.target, so both are closed by the same refusal.

3.3 What is left — [MEASURED-PVE]. init=/bin/bash works.

Appending init=/bin/bash to the kernel line in the GRUB editor and booting produced:

Found volume group "pve" using metadata type lvm2
3 logical volume(s) in volume group "pve" now active
/dev/mapper/pve-root: clean, 60332/561936 files, 1141450/2243584 blocks
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell
root@(none):/#

An unauthenticated root shell on a locked-root system. No password is involved because PID 1 is the shell — sulogin is never reached.

Path Works? Realistic for whom
PVE web console (:8006) No (§2.3)
GRUB (recovery mode)sulogin No (§3.2)
GRUB editor → init=/bin/bash Yes (measured) Operator only. Requires catching a 1-second menu, editing a kernel line, and knowing the incantation. Not a non-technical customer action.
Live USB / rescue media not tested Operator only
SSH by key n/a Only if a key were baked — which R-142 says must not happen in a public image

So a locked-root box is recoverable, but only by someone competent at the physical console. That is a materially weaker objection than "no path back at all", which is what the structural reading suggested before it was measured.

A security corollary that is worth stating plainly: the installed GRUB has no password, so locking root buys nothing against physical access. Anyone at the keyboard can take a root shell in under a minute. Locked root is a defence against remote credential reuse — the shared-ISO-password problem — and not a physical-security measure. R-152.

3.4 Does locked differ from rotated? — [MEASURED]. Yes, at exactly one point.

  • Locked (*/!)sulogin refuses (§3.2); the web UI refuses (§2.3); init=/bin/bash works.
  • Rotated to a per-box valuepam_unix accepts the correct password (the §2.2 control proves this) and the web UI issues a ticket for it (the §2.3 control proves this), so both the console and the browser open to whoever holds it; init=/bin/bash works as well.

And this is what settles the escrow question. A rotated password that is discarded is operationally identical to a locked account: nobody holds it, so sulogin and the web UI refuse either way. The only thing escrow buys is that the operator keeps the web-UI and sulogin routes. It does not buy recoverability as such, because init=/bin/bash is available in every case.

3.5 A probe that failed, recorded as failed

The first GRUB attempt waited 3 seconds before acting; the menu's timeout is 1 second, so the system had already booted and the keystrokes went into the login prompt — the arrow-key escape sequences (^[[B) are visible there in the screendump. It tested nothing. A later attempt typed init=/bin/bash onto the echo line one below linux, which the pre-typing verification screendump caught before it was booted. Both were rerun; the numbers above are from the corrected runs.


4. Q3 — does the Felhom stub survive before-network?

4.1 Does it execute? — [MEASURED-PVE]. Yes.

The diagnostic emits @@SPIKE2_HOOK_RAN@@ BEGIN/END to ttyS0. On scenario B (ordering = "before-network") both markers are present (count 2). This is a positive observable the hook produced, which §4.4 explains is the only acceptable form of proof here.

4.2 Does it have what it needs that early? — [MEASURED-PVE]. Yes, everything.

4.3 Does it complete before anything is network-reachable? — [MEASURED-PVE + STRUCTURAL]. Yes.

The A/B comparison, same image, same diagnostic, only ordering differs:

Diagnostic A — fully-up B — before-network
hook markers 2 (ran) 2 (ran)
uptime_s at hook 18.32 6.58
entropy_avail 256 256
etc_writable YES YES
openssl/chpasswd/systemctl/passwd/curl/ip all present all present
openssl_rand_len 32 32
ip_global vmbr0 10.0.2.15/24 (empty)
network.target active inactive
network-online.target active inactive
pveproxy active inactive
sshd active inactive
listen_22_8006 3 0

At before-network the hook runs with entropy, a writable /etc, every binary it would need, and a demonstrated ability to generate a 32-character random string — while the box has no IP address and nothing is listening on :22 or :8006. A credential rotation placed there has a zero-length exposure window, measured.

Equally, the fully-up column is the measured proof of the converse: today's ordering runs the hook when sshd and pveproxy are already listening. The window Spike 1 argued for structurally is now observed.

The structural half agrees and generalises it. The shipped unit (proxmox-first-boot 9.2.5, /usr/lib/systemd/system/proxmox-first-boot-network-pre.service):

After=systemd-remount-fs.service
Before=network-pre.target
Wants=network-pre.target
ConditionPathExists=/var/lib/proxmox-first-boot/pending-first-boot-setup
ConditionPathIsReadWrite=/var/lib
Type=oneshot

Before=network-pre.target + Type=oneshot makes it a hard barrier ahead of all network configuration — so this holds on every machine, not just this VM. Both installs then booted to full reachability (SSH +20 s, pveproxy +2629 s), so the early hook does not break the boot.

A third ordering value exists that Spike 1 did not record. validate-answer accepts before-network, network-online, fully-up; it rejects network-pre, multi-user and a bogus value, so this is a real enum. The shipped package contains a matching unit for each. network-online is the unevaluated middle option. R-149.

4.4 Does the existing pairing flow still work? — [STRUCTURAL], and it is the one gap left

Not measured. The scenarios installed the spike's own diagnostic as the first-boot hook, not Felhom's stub-first-boot.sh, so stub-plus-pairing coexistence is argued, not observed.

The structural case is strong and has a correction in it:

The stub does not need fully-up. stub-first-boot.sh writes three files, then systemctl daemon-reload, enable, and start --no-block felhom-bootstrap.service. A grep for pvesh, pct, pveum and qm across it returns no match (rc=1). The unit it starts carries its own After=network-online.target pve-cluster.service pveproxy.service, so --no-block queues the job and systemd holds it until those are satisfied.

So answer.toml.tmpl:27 justifies the current ordering with a dependency the stub does not have — it reads "fully-up = the stub runs after pveproxy so pvesh/pct work", but that usage lives in felhom-bootstrap.sh, which never runs under the stub's ordering. A comment asserting a dependency that does not exist, and it has constrained the design ever since. R-148.

Why §4.1's positive marker is load-bearing. All four proxmox-first-boot-* units are guarded by ConditionPathExists= / ConditionPathIsReadWrite=, and in systemd a failed condition is a skip, not a failure — no error, successful boot. A hook that never ran and one that ran perfectly are indistinguishable from the absence of errors. R-150.


5. Q4 — the boot menu, and which entry is the default

5.1 Where the collapse happens — [STRUCTURAL], and it is not a simple removal

iso-repack.sh replaces the stock grub.cfg wholesale, rendering grub/grub.cfg.tmpl over it (:144-148) after lifting the linux/initrd lines verbatim from the stock Install Proxmox VE (Automated) entry (:108-131) so a PVE bump tracks automatically.

Four independent gates enforce the single entry:

Gate Location Effect
exactly one menuentry :153-155 exit 14
zero submenu :156 exit 14
no live proxtui, proxdebug, nomodeset, Rescue Boot, memtest, fwsetup :160-164 exit 14
re-extracted from final.iso: one entry, theme present, entry named Felhom telepítés :314-319 exit 15

The fourth proves the menu in the shipped image rather than the build tree — the seam-wiring lesson applied correctly.

What a second entry would cost: the two count gates move from == 1 to == 2, or to a named allowlist. The banned-token gate passes unchanged for a stock Install Proxmox VE (Graphical) entry — it uses none of those six tokens. The safety property the gates exist for (no route to a debug/rescue/manual-disk-picker path) survives a second graphical-installer entry intact.

5.2 Are default and timeout settable? — [STRUCTURAL]. Already are.

grub/grub.cfg.tmpl sets set timeout_style=menu, set timeout=5, set default=0. The template also records a real defect it fixed: the stock PVE config writes timeout-style (hyphen), which GRUB does not recognise as a variable name, so Felhom's underscore spelling is the one that works.

5.3 The design question — framed with evidence, deliberately not ruled

For interactive-as-default. Spike 1 proved the automated path cannot be made safe on unseen hardware: no property distinguishes a system disk from a customer's backup drive, and a filter matching two disks silently wipes one. A tester who boots and walks away on multi-disk hardware is walking away from an unattended destructive operation whose target cannot be predicted.

This session strengthens that case independently. If the public image also ships a locked root, then a box whose install succeeds but whose pairing fails has no web UI (§2.3) and no working recovery-mode shell (§3.2) — only an operator-grade init=/bin/bash (§3.3). An unattended default maximises the number of testers who reach that state without having read anything.

Against. Unattended install is a selling point and most first boxes are single-disk, where the automated path is fine.

Operator-built and public need not match, and two profiles from one build is cheap. An operator-built image targets known disks, so automated-default is safe there; the public image is the one facing unseen hardware. That split is available however the default is ruled.


6. Q5 — the resulting shape, as constraints

Constraints on a build spec, each with a release-gate criterion. Not an implementation.

# Constraint Release gate
1 No disk selection by property on unseen hardware (Spike 1 §3.2/§3.3). Rendered answer contains no filter.* key, or the image is operator-built for a named machine.
2 The interactive installer must be reachable from the boot menu (§5.1). final.iso's grub.cfg has ≥1 interactive entry and still no live proxtui/proxdebug/nomodeset/Rescue Boot/memtest/fwsetup — keep gate 3, relax gates 1 and 4 to an allowlist.
3 No usable baked credentialroot-password-hashed = "*" installs cleanly (§1, three scenarios). Rendered root-password-hashed is exactly *, and no .rootpw.txt is emitted. Two one-line assertions.
4 Any per-box password is set at before-network — the only ordering with a measured zero window (§4.3). Rendered [first-boot].ordering is before-network, and the hook's positive marker is observed in a boot test (§4.4, R-150).
5 No root-ssh-keys, ever, in a public image (R-142). grep -c root-ssh-keys on the rendered answer is 0.
6 The profile must be committed — demo-felhom's baked key comes from an uncommitted profile, so a reference box is not reproducible from main. --profile resolves inside the repo tree and git status --porcelain is empty for it. R-151.
7 The artefact must be reproducible (R-144: nested-probe's profile is gone). Manifest records the committed profile path + repo commit; both resolve.
8 The recovery path must be documented, and named operator-only if it is. §2.3 + §3.2 + §3.3: with a locked root the only way in is init=/bin/bash at the console. Release notes state it, in those terms.

What the image must not contain: a usable root password hash, any root-ssh-keys, any customer id, claim code, retrieval passphrase or hub token, and any filter.* selector in the public variant. Spike 1 §6 established by enumeration that /answer.toml is the only secret-bearing file, so gates 3 and 5 over that one rendered file cover the credential surface.


7. The operator's decisions

Decision 1 — escrow or discard a per-box root password. §3.4 reshapes this twice over:

  • Discard and lock are the same thing for recovery — if nobody holds the password, both sulogin and the web UI refuse.
  • Neither makes a box unrecoverable. init=/bin/bash works in every case (§3.3), so an operator at the console can always get in. The earlier fear that locking root leaves "no path back at all" is measured to be false.

So what escrow actually buys is narrower than it looked: the operator keeps the web-UI route (§2.3) and the sulogin route (§3.2) without needing physical presence. Against that stands D6 — a hub holding console root for the whole fleet is exactly what the hub alone must not be.

Recommendation, offered because the measurements now support one: discard/lock for the public image, and accept operator-only physical recovery, because the credential that would be escrowed is the same one whose fleet-wide sharing started this whole line of work. Escrow remains defensible for operator-built boxes, where the operator installed the machine anyway. The decision is still the operator's; it is no longer blocked on a measurement.

Decision 2 — the default boot entry for a public image. Framed in §5.3, unruled. The disk evidence and the recovery evidence now point the same way (interactive), and the cost is the unattended experience. The operator-built-versus-public split is cheap and available either way.


8. What is still unknown, and what it would take

# Unknown What it takes
1 Felhom's real stub at before-network (§4.4) — coexistence with pairing is structural, not observed. The single remaining gap in Q3. Build a probe ISO with stub-first-boot.sh as the hook and ordering = "before-network", boot, confirm felhom-bootstrap.service still starts and pairing completes. ~40 min. Do this before the spec.
2 Whether root SSH is refused because of the lock (§2.2) — the probe used a wrong password in the control too, so it discriminates nothing. Re-run the SSH leg with the control's correct password. ~10 min.
3 What else in PVE assumes a usable root credential (§2.4) — cron, backup jobs. Inspect a locked install over a longer uptime.
4 What network-online ordering buys (§4.3) — a real third option, never evaluated. One extra scenario.
5 Whether the multi-match pick is deterministic by enumeration order (Spike 1, still open). Re-run Spike 1's S1 with reversed disk order. ~15 min.
6 Whether the installer excludes its own boot medium (Spike 1, still open). USB boot on real hardware — HUMAN.
7 Whether a relaxed-gate two-entry menu actually boots the stock installer (§5.1). Not attempted — it would mean changing repo files, which §4 forbids.

9. R-rows opened

Continuing from R-147 (Spike 1).

R Severity Finding
R-148 MEDIUM answer.toml.tmpl:27 justifies ordering = "fully-up" with a dependency the stub does not have. stub-first-boot.sh contains no pvesh/pct/pveum/qm call (grep rc=1); that usage is in felhom-bootstrap.sh, under its own After=network-online.target … pveproxy.service unit. A comment asserting a dependency rather than an invariant — and it blocked the ordering that §4.3 now measures as giving a zero credential window.
R-149 LOW [first-boot].ordering has three values, not twobefore-network, network-online, fully-up validate; network-pre and multi-user are rejected. The middle option has never been evaluated.
R-150 MEDIUM A first-boot hook that never runs is indistinguishable from one that succeeded. All four proxmox-first-boot-*.service units are Condition*-guarded, and a failed condition is a systemd skip, not a failure — no error, successful boot. Any credential-rotation-in-first-boot design must be verified by a positive marker the hook emitted.
R-151 MEDIUM A reference box is not reproducible from the repository. demo-felhom was installed from an ISO built with the uncommitted n100-demo.profile, which is where its baked SSH key comes from (Spike 1 §4.6). Gate 6 in §6 exists to stop a public image inheriting that shape.
R-152 LOW A locked root is not a physical-security measure, and should never be described as one. The installed PVE GRUB has no password (§3.1), so init=/bin/bash yields an unauthenticated root shell in under a minute (§3.3, measured). Locking root defends against remote reuse of a shared ISO credential and nothing else. If a physical-access threat model is ever wanted, it needs a GRUB password, which is a separate decision with its own lockout risk.

10. Teardown

All three layers, each verified positively.

Layer 1 — demo-hp.

Check Result
QEMU processes 0
/mnt/nvme-1tb/spike2/ removed — No such file or directory
/tmp/paia removed
loop devices / /mnt/s2ans 0 / removed
/mnt/nvme-1tb/ contents dump e2d-images felhom-data images lost+found — the pre-spike set
disk usage 6.6 G — identical to pre-spike
drill-r50 (VM 300) stopped, untouched (R-93 fixture intact)
guest 9201 running, untouched
/etc/pve/storage.cfg 4 storages, unchanged; no scratch storage ever registered
local-lvm never written

Layer 2 — demo-felhom. /tmp/paia removed; guest 9201 running; the external USB drive 931.5G usb /mnt/hdd_1 still attached and mounted exactly as found — never attached, detached, partitioned or written. Only device-info reads were ever performed there.

Layer 3 — DooPlex.

Check Result
scratchpad 1.7 GB → 60 K (probe ISO, screendumps, squashfs/.deb extracts, schema test dirs)
control password (s2/.ctlpw) and scen/C.toml shred -u'd / removed; verified absent. Never printed, never committed
mechanism-probe container docker run --rm; no global Docker cleanup run (fence)
felhom-iso/out/ untouched, 17 ISOs
production files none modified — no build-felhom-iso.sh, no iso-repack.sh, no profile, no felhom-bootstrap.sh

Hub-side disposition — nothing to dispose. No customer, host or appliance was created: the nested VMs ran under plain QEMU with user-mode networking and no route to the hub, and no install ever contacted it. R-131 gains no row.

Published artefacts: none. Nothing went to R2.