Release signing for
@automagik/genie is cosign keyless only. There is no long-lived private key, no hardware-backed offline key, no public-key fingerprint to pin. “Rotation” here means rotating the certificate-identity pin (workflow-path@ref, OIDC issuer, and provenance source-uri) that operators cross-check across three independent channels.When to rotate
Rotate the pinned certificate identity when — and ONLY when — one of these happens. Routine releases do NOT require rotation.- Workflow path moves.
.github/workflows/release.ymlis renamed or split. The Fulcio certificate SAN embeds the workflow path, so a move changes the certificate identity verifiers must accept. - Repository is renamed.
automagik-dev/geniebecomes something else. Both the signer identity regexp and the SLSA provenancesource-urimust move atomically. - OIDC issuer changes. GitHub Actions’ OIDC token issuer URL changes
(rare; announced upstream). The pinned
certificate-oidc-issuermust change with it. - Keyless trust-root incident. Sigstore / Fulcio announces a trust-root rotation that requires consumers to re-verify against a new root. Operators follow the cosign upstream rotation advisory in addition to this runbook.
Rotation contract (summary)
If any of the five constraints above cannot be met, the rotation does not
ship. Do NOT reduce the grace period to “fix” a broken window.
Step-by-step rotation procedure
Phase 1 — Pre-rotation (before touching anything)
- Open a tracking issue in
automagik-dev/genietitledSIGNING_CERT_IDENTITY_<YYYYMMDD>using the.github/ISSUE_TEMPLATE/signing-key-fingerprint.mdtemplate. - Draft the new values:
certificate-identity-regexpcertificate-oidc-issuer- provenance
source-uri
- Confirm the CURRENT values are byte-identical across all three pinning channels. If they already drift, stop and open an incident — rotation cannot overlay a broken baseline.
Phase 2 — Two-officer ceremony
- Two Namastex security officers (distinct GitHub identities, distinct
hardware keys for commit signing) co-author the rotation PR. The PR MUST:
- Update
.github/workflows/release.yml(if workflow path changes). - Update
src/term-commands/sec.tsSIGNER_IDENTITY_REGEXP/SIGNER_OIDC_ISSUER/PROVENANCE_SOURCE_URIconstants. - Update
SECURITY.mdso its pinned values match the new identity byte-for-byte. - Update
/.well-known/security.txton the project site (or file the site-repo PR in parallel). - Update (or open) the tracking issue created in Phase 1 with the final values.
- Update
- Both officers sign the PR via
git commit -Swith a GPG key that appears on their GitHub profile. CI checks for exactly two distinct co-authors with verified signatures; a single-officer PR must be refused. - After merge, a tagged release runs the real signing workflow against the
new certificate identity. The post-release verify job
(
.github/workflows/release.ymlverifyjob) MUST pass; if it fails, the rotation is aborted and rolled back.
Phase 3 — Grace-period dual-verification
- For at least 72 hours after the rotation lands, verification tooling MUST
accept BOTH the old and new certificate identities. In practice that means
keeping the old entry in
SECURITY.mdunder a## Previous pinningheading so operators running the previous release do not failgenie sec verify-install. - The pinned issue is updated with the old value under its
## Previous pinningsection — never deleted. - Operators running
genie sec verify-install --offlineduring the grace period should seeverified_attimestamps newer than the rotation epoch. Any operator whoseverified_atpredates the rotation is instructed to pull the new release.
Phase 4 — Retirement of the old identity
- 72 hours after Phase 3 begins, and AFTER the post-rotation release has
been verified end-to-end at least once by each of the three channels, the
old identity is retired:
SECURITY.mddrops the## Previous pinningsection.- The tracking issue is marked RESOLVED (but never deleted).
- Verification tooling stops accepting the old identity.
- Retirement is a separate PR. It is NOT bundled with the rotation PR — a bundled retirement eliminates the grace window.
Test-key dry-run recipe
The rotation procedure MUST be practiced at least once per quarter using throwaway test identities. A successful dry-run ends withgenie sec verify-install returning exit 0 against a test-release bundle
signed by the rehearsed identity.
Goal
Simulate Phases 1–4 end-to-end in under one hour, against a disposableautomagik-dev/genie-signing-drill repo (or a local fork pointing at a
fixture workflow), without touching the production signing identity.
Steps
Acceptance
A dry-run is successful when every expected exit code in the recipe above appears. If any step deviates, file an issue taggedsigning-rotation-drill
and do not ship the rotation PR until the deviation is understood.
Anti-patterns
The following have bitten prior rotations and MUST NOT be repeated:- Rotating without updating all three channels. The three-channel pin exists so an attacker must compromise three distribution points to invalidate. Dropping one channel defeats the invariant.
- Single-officer rotation. A rotation touched by one human account means a single compromise can move the pin. CI blocks single-officer PRs.
- Grace-period shortcuts. Reducing the 72-hour window to “speed up” a
rotation makes operators on older releases fail
verify-install. If a rotation is urgent enough to skip the grace window, it is an incident — file it as such, don’t rotate. - Bundling retirement with rotation. Retirement collapses the grace window. Always two PRs.
- “Just amend” fixups. Rotation history is load-bearing. Fix via a new PR that explicitly references the broken rotation; do not rewrite history.
References
.github/cosign.pub— the documented NO-KEY sentinel (keyless-only)..github/workflows/release.yml— the signing + verify pipeline..github/ISSUE_TEMPLATE/signing-key-fingerprint.md— pinned-issue template.src/sec/unsafe-verify.ts— the--unsafe-unverifiedcontract that operators fall back on if a rotation goes sideways.scripts/verify-release.sh— the local verification script that mirrorsgenie sec verify-install.