Skip to content

fix: second audit — reliable rebuild exit detection, harmonia wait, closure+minisig verification - #174

Merged
mdheller merged 1 commit into
mainfrom
fix/enroll-second-audit
Jun 16, 2026
Merged

fix: second audit — reliable rebuild exit detection, harmonia wait, closure+minisig verification#174
mdheller merged 1 commit into
mainfrom
fix/enroll-second-audit

Conversation

@mdheller

Copy link
Copy Markdown
Contributor

Second audit findings and fixes

C1 — nixos-rebuild exit code swallowed (CRITICAL, both passes)

The original pattern:

nixos-rebuild ... | tee LOG | grep ... || true
if grep -qiE '(error:|FAILED)' LOG; then die; fi

The || true on the grep masks the entire pipeline's exit code under set -o pipefail. Log-pattern matching was the only failure gate — and patterns like "error:" can appear in warnings that don't indicate actual failure, while real failures can produce error messages that don't match.

Fixed by capturing PIPESTATUS[0] (nixos-rebuild's actual exit code) on the line immediately after the pipeline. The || true on grep does not reset PIPESTATUS — it applies only to grep's own exit status in the list, not to the pipeline array. Both pass-1 and pass-2 now fail closed on any non-zero nixos-rebuild exit.

H1 — Step 11 harmonia wait was sleep 3 (HIGH)

A 3-second sleep before checking harmonia was racey — harmonia routinely takes 5-15s to start after activation. If the check failed, the cache push was skipped with a warn. Replaced with a 60s polling loop (12 × 5s) that checks the actual HTTP endpoint before attempting nix copy. Push failure remains warn-only since syncd repopulates the cache on its first cycle.

H2 — Step 10 nix build failure undetected (HIGH)

--print-out-paths 2>&1 | tail -1 mixed stderr (Nix build progress) into the captured store path. On failure, tail -1 would capture a Nix error message rather than a store path. Fixed to 2>/dev/null (suppress build progress) + explicit guard: [[ -n "${CLOSURE}" && -e "${CLOSURE}" ]] before proceeding.

M1 — Step 12 minisig existence check, not verification (MEDIUM)

The enrollment verification step checked if the minisig endpoint responded, but did not run minisign -V to verify the signature was cryptographically valid. A stale sig or a sig generated with a different key would pass. Now downloads the sig to a tmpfile and verifies it against the local public key — same approach as doctor.sh.

M2 — No generation verification after pass-2 (MEDIUM)

After pass-2 nixos-rebuild switch, there was no confirmation that /run/current-system pointed to the closure built in step 10. Added readlink /run/current-system comparison — warns if they differ (e.g., nixos-rebuild cached an older derivation).

@mdheller
mdheller merged commit f8ffa2d into main Jun 16, 2026
@mdheller
mdheller deleted the fix/enroll-second-audit branch June 16, 2026 18:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant