Deferred bug-hunt findings: harden CI workflows + tooling (pre-1.5) #2

Merged
zach merged 2 commits from fix/deferred-ci-hardening into main 2026-05-29 06:06:13 +00:00
Owner

Follow-up to #1 — the deferred findings from the same Opus 4.8 bug hunt, cleared before cutting the v7.0.10-hardened1.5 validation build so that build exercises a complete pipeline. All validate.yml gates pass, plus targeted tests (tag-regex, redaction, release-id lookup, prune retention) and a run of the real intent-matches-policy gate against the live configs.

Workflows (69e45c9)

  • A3 — repro-check never auto-ran. Forgejo Actions doesn't support workflow_run (GitHub-only; confirmed against the Forgejo docs), so the post-publish repro gate silently never fired — only the nightly cron ran. Replaced with workflow_dispatch (cron unchanged).
  • A4 — same-tag rebuilds could fail. The 409/422 fallback used GET /releases/tags/{tag}, which filters DRAFT releases on some Forgejo versions (ours are created as drafts) -> null id -> a valid rebuild fails. Resolve the id from the releases LIST instead (reliably includes drafts for a write-scoped token).
  • B4 — opt-in isolation gate. REQUIRE_NET_ISOLATION=1 (repo variable) hard-fails when unshare -n is unavailable, instead of silently compiling without network isolation while the signing key is in tmpfs. Default behavior unchanged.
  • B6 — tag regex. Reject .0 packaging tags (dpkg would sort them above the bare -hardenedN) and leading zeros; X.Y.Z still allows the 0 in 7.0.10.
  • C11 — parity guard. Fail intent-matches-policy if intent.config parses to zero symbols. The originally-suspected substring false-pass was investigated and does NOT occur (comm matches whole symbols), so no change there.
  • publish.yml — noted the instance now runs Forgejo 15.0.2 (the release-trigger note was verified on 12.10.1).

Tools (cad9619)

  • publish.py / prune.py — auth-redaction regex now tolerates indented / JSON-quoted Authorization headers, so a token can't slip into a CI log via an echoed non-line-leading header.
  • sbom.py — record <unavailable> instead of a silent blank when a toolchain probe fails, so the gap is visible in the provenance SBOM.
  • repro-check.sh — retry a transient input fetch before giving up, and word an unreachable input (WARN: transient/removal) distinctly from a HASH DRIFT (FAIL) — only a mismatch means the recorded bytes changed under us.

After merge: cut v7.0.10-hardened1.5 as the first end-to-end validation of the seed-era pipeline (watch the new hardening-drift step vs the pre-seed baseline; run repro-check via workflow_dispatch after publish).

🤖 Generated with Claude Code

Follow-up to #1 — the deferred findings from the same Opus 4.8 bug hunt, cleared before cutting the `v7.0.10-hardened1.5` validation build so that build exercises a complete pipeline. All `validate.yml` gates pass, plus targeted tests (tag-regex, redaction, release-id lookup, prune retention) and a run of the real `intent-matches-policy` gate against the live configs. ## Workflows (69e45c9) - **A3 — repro-check never auto-ran.** Forgejo Actions doesn't support `workflow_run` (GitHub-only; confirmed against the Forgejo docs), so the post-publish repro gate silently never fired — only the nightly cron ran. Replaced with `workflow_dispatch` (cron unchanged). - **A4 — same-tag rebuilds could fail.** The 409/422 fallback used `GET /releases/tags/{tag}`, which filters DRAFT releases on some Forgejo versions (ours are created as drafts) -> null id -> a valid rebuild fails. Resolve the id from the releases LIST instead (reliably includes drafts for a write-scoped token). - **B4 — opt-in isolation gate.** `REQUIRE_NET_ISOLATION=1` (repo variable) hard-fails when `unshare -n` is unavailable, instead of silently compiling without network isolation while the signing key is in tmpfs. Default behavior unchanged. - **B6 — tag regex.** Reject `.0` packaging tags (dpkg would sort them above the bare `-hardenedN`) and leading zeros; `X.Y.Z` still allows the `0` in `7.0.10`. - **C11 — parity guard.** Fail `intent-matches-policy` if intent.config parses to zero symbols. The originally-suspected substring false-pass was investigated and does NOT occur (`comm` matches whole symbols), so no change there. - **publish.yml** — noted the instance now runs Forgejo 15.0.2 (the release-trigger note was verified on 12.10.1). ## Tools (cad9619) - **publish.py / prune.py** — auth-redaction regex now tolerates indented / JSON-quoted `Authorization` headers, so a token can't slip into a CI log via an echoed non-line-leading header. - **sbom.py** — record `<unavailable>` instead of a silent blank when a toolchain probe fails, so the gap is visible in the provenance SBOM. - **repro-check.sh** — retry a transient input fetch before giving up, and word an unreachable input (WARN: transient/removal) distinctly from a HASH DRIFT (FAIL) — only a mismatch means the recorded bytes changed under us. After merge: cut `v7.0.10-hardened1.5` as the first end-to-end validation of the seed-era pipeline (watch the new hardening-drift step vs the pre-seed baseline; run repro-check via `workflow_dispatch` after publish). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- repro-check.yml (A3): drop the `workflow_run` trigger — Forgejo Actions
  does not support it (GitHub-only), so the post-publish repro gate
  silently never fired and only the nightly cron ran. Replace with
  `workflow_dispatch` so an operator can verify a fresh release on demand;
  the cron still covers everything within 24h.
- build-kernel.yml (A4): on a same-tag rebuild the 409/422 fallback did
  `GET /releases/tags/{tag}`, which has filtered DRAFT releases on some
  Forgejo versions (ours are created as drafts) -> null id -> a valid
  rebuild fails. Resolve the id from the releases LIST instead, which
  reliably includes drafts for a write-scoped token.
- build-kernel.yml (B4): add an opt-in `REQUIRE_NET_ISOLATION=1` repo
  variable that hard-fails when `unshare -n` is unavailable, instead of
  silently compiling without network isolation while the signing key is
  in tmpfs. Default behavior unchanged.
- build-kernel.yml (B6): tighten the tag regex so hardenedN and the
  optional .P are [1-9][0-9]* — reject ".0" packaging tags (which dpkg
  would sort above the bare -hardenedN) and leading zeros. X.Y.Z keeps
  [0-9]+ so the "0" in 7.0.10 stays valid.
- validate.yml (C11): guard intent-matches-policy against a vacuous pass
  when intent.config parses to zero symbols. (The token comparison is
  already exact — comm matches whole symbols — so the originally-suspected
  substring false-pass does not occur; verified.)
- publish.yml: note the instance now runs Forgejo 15.0.2 (the release-
  trigger behavior was verified on 12.10.1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tools: defensive cleanups (deferred bug-hunt findings)
All checks were successful
validate / shellcheck (pull_request) Successful in 12s
validate / yamllint (pull_request) Successful in 11s
validate / pycompile (pull_request) Successful in 4s
validate / intent-matches-policy (pull_request) Successful in 4s
validate / no-placeholder-digests (pull_request) Has been skipped
cad9619413
- publish.py / prune.py: the auth-redaction regex only matched a header
  at the start of a line; broaden it to tolerate leading whitespace and
  JSON-style quoting (`  "Authorization": ...`) so a token can't slip into
  a CI log via an echoed indented/quoted header.
- sbom.py: record "<unavailable>" instead of "" when a toolchain version
  probe fails, so a gap is visible in the provenance SBOM rather than a
  silent blank.
- repro-check.sh: retry the input-hash fetch a few times before giving up,
  and word an unreachable input as transient/removal (WARN) distinctly
  from a HASH DRIFT (FAIL) — only a mismatch means the recorded bytes
  changed under us, which is the security-relevant signal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zach merged commit ab2dce46ef into main 2026-05-29 06:06:13 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
unredacted/linux-hardened-unredacted!2
No description provided.