Skip to content

🔒 fix(release): pin cosign signer identity and issuer in install.sh (SEC-03) - #80

Merged
konih merged 3 commits into
mainfrom
lane/aud2-s03-integrate
Aug 18, 2026
Merged

🔒 fix(release): pin cosign signer identity and issuer in install.sh (SEC-03)#80
konih merged 3 commits into
mainfrom
lane/aud2-s03-integrate

Conversation

@konih

@konih konih commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Integration branch for AUD2-S03 (SEC-03) — rebased lane/aud2-s03 (6a61a17) onto origin/main (39333fd) with the integrator-owned CHANGELOG.md regeneration amended in.

What this closes

hack/install.sh invoked cosign verify-blob without --certificate-identity-regexp / --certificate-oidc-issuer. On cosign v2+ that either errors out (making --require-signature a broken promise) or accepts any Fulcio identity — a mirror-swapped archive+bundle pair verified clean.

The pin

--certificate-oidc-issuer https://token.actions.githubusercontent.com
--certificate-identity-regexp '^https://github\.com/PlatformRelay/[Aa]ssent/'

Byte-identical in hack/install.sh and both SECURITY.md cosign blocks, held by a drift gate.

The [Aa] is deliberate (D-153): the repo was renamed assentAssent between v0.1.0 and v0.2.0, the Fulcio SAN carries GitHub's canonical casing, and cosign matches the identity regexp case-sensitively (Go RE2). The previously published lowercase value rejected the project's own v0.2.0 and v0.3.0 artifacts. Dots are also escaped (they were metacharacters). The ^ anchor and owner/repo scope are unchanged, so evil-mirror, assent-mirror, PlatformRelayEvil, GitLab and githubXcom all still fail.

Verification

  • task checkexit 0, all 18 stages.
  • bash hack/release/install_cosign_pin_test.shexit 0. Proves both pins load-bearing by mutation (deleting either re-opens SEC-03), and validates against committed real-SAN fixtures decoded from the published v0.1.0/v0.2.0/v0.3.0 bundles.
  • task ci-audit-testexit 0.
  • New stage release-install-cosign-pin-test is present in both Taskfile.yml's check: list and CHECK_STAGES; both count 18 (AUD-S18 / RELSE-08 lockstep verified).

Review record

REQUEST CHANGES at ba9242c (documentation only) → doc round → APPROVE at 6a61a17 on fresh re-review. No P0/P1 outstanding. Original lane/aud2-s03 stays on origin at 6a61a17 as the review record.

konih added 3 commits August 19, 2026 01:01
…SEC-03)

`hack/install.sh` ran `cosign verify-blob --bundle "$bundle" "$ARCHIVE"` with no
signer pin. Keyless verification without `--certificate-identity-regexp` either
errors out (cosign v2 requires the flags) or accepts ANY Fulcio certificate — so
a mirror-swapped archive shipped with its own validly signed bundle verified
clean and `--require-signature` promised a guarantee it did not deliver, while
SECURITY.md's published copy-paste instructions pinned both flags.

install.sh now carries the pair byte-identically to SECURITY.md:

    --certificate-oidc-issuer https://token.actions.githubusercontent.com
    --certificate-identity-regexp '^https://github.com/PlatformRelay/assent/'

New gate `hack/release/install_cosign_pin_test.sh`, offline (stubbed `cosign`
first on PATH, fake .sigstore.json bundles, no network, no real Fulcio):

  * REQ-AUD2-S03-01 — both flags are inside the folded cosign invocation
    (comment lines excluded, so prose cannot satisfy the assertion).
  * REQ-AUD2-S03-02 — a bundle whose certificate identity is
    github.com/evil-mirror/assent fails `install.sh --require-signature` and
    writes no binary to --dest; likewise a foreign OIDC issuer. A matching
    identity still installs (positive control), and the stub log proves both
    flags reach cosign's argv rather than merely existing in the file.
  * REQ-AUD2-S03-03 — non-vacuity in both halves: deleting either flag from a
    temp copy reddens the static assertion, AND the un-pinned copy installs the
    foreign-signed archive the fixed script rejects, so the pin is load-bearing.
  * REQ-AUD2-S03-04 — drift gate: issuer and identity regexp are extracted from
    both hack/install.sh and SECURITY.md (each must yield exactly one distinct
    value) and compared; rewriting either file alone reddens.
  * REQ-AUD2-S03-05 — wired as its own `task check` stage
    `release-install-cosign-pin-test`, with a self-pin plus the AUD-S18
    CHECK_STAGES entry so the release exit gate grades it.

Out of scope, noted for follow-up: hack/release/verify-artifacts.sh:124 runs the
same unpinned `cosign verify-blob --bundle` on the maintainer/CI verification
path.
…ng (SEC-03)

The pin landed in b616d00 copied SECURITY.md's published value faithfully and
was still wrong. The repository was renamed PlatformRelay/assent ->
PlatformRelay/Assent between v0.1.0 and v0.2.0; the Fulcio SAN carries GitHub's
canonical casing, and cosign matches --certificate-identity-regexp
case-SENSITIVELY. Decoded from the published bundles (openssl x509 on the
bundle's certificate, X509v3 Subject Alternative Name):

  v0.3.0  https://github.com/PlatformRelay/Assent/.github/workflows/release.yaml@refs/tags/v0.3.0
  v0.2.0  https://github.com/PlatformRelay/Assent/.github/workflows/release.yaml@refs/tags/v0.2.0
  v0.1.0  https://github.com/PlatformRelay/assent/.github/workflows/release.yaml@refs/heads/main

So the lowercase-only pin verified v0.1.0 and REJECTED the current releases:
`install.sh --require-signature` would have failed closed against this project's
own artifacts, and SECURITY.md's copy-paste instructions were already broken the
same way. Both files now publish, byte-identically (one truth, REQ-AUD2-S03-04):

    --certificate-identity-regexp '^https://github\.com/PlatformRelay/[Aa]ssent/'

The dots are escaped in the same edit — they were regexp metacharacters matching
any character, so `github.com` also matched e.g. `githubXcom`. The pin is not
widened beyond the org/repo: the `^` anchor stays, there is no `.*`, and another
owner, an `assent-mirror` typosquat or another forge still fail.

Gate changes (hack/release/install_cosign_pin_test.sh):

  * REQ-AUD2-S03-01/02 — new section 4c: the SANs above are committed as
    FIXTURES (public certificate contents) and every one is checked twice — as a
    regexp match and end-to-end through install.sh with the stub cosign. Six
    negative fixtures cover another owner, a typosquat, another org, another
    forge, a lost anchor and an unescaped-dot host. Section 4d re-runs the accept
    table against the pre-fix lowercase pin and requires it to reject at least
    two, so the fixture check is proved capable of catching this exact bug.
  * REQ-AUD2-S03-04 — the drift mutations no longer interpolate the pinned value
    into a sed pattern (it is itself a regexp now, which would have matched
    nothing and silently stopped mutating); the rewrites are flag-relative.
  * The stub self-test (section 4) now uses synthetic values instead of the
    project's pin, so a wrong pin reddens in 4b/4c naming the casing rather than
    in 4 blaming the stub. It also asserts the stub is permissive when unpinned,
    which is what makes 5c's "the pin is load-bearing" demonstration valid.

Reported, deliberately NOT fixed here (not an owned path):
hack/release/verify-artifacts.sh:124 runs the same unpinned
`cosign verify-blob --bundle` on the maintainer/CI path.
…C-03)

Doc round for AUD2-S03. No code change — hack/install.sh, SECURITY.md and
hack/release/install_cosign_pin_test.sh are untouched here.

openspec/specs/p5-aud2-audit-remediation/spec.md — the spec as written forbade
what shipped, so it is amended in place (precedent: 1ac5692, b08be34 — spec-first
still means the spec tracks what was measured):
  * "Reuse, explicitly" said SECURITY.md's flag pair was frozen and "the script
    must not invent a different issuer or identity pattern". The published pair
    was itself wrong, so mirroring it could not stay literal. The intent is kept,
    restated: the pin is verified against committed real-SAN fixtures, never
    chosen freely.
  * The AUD2-S03 Goal still quoted '^https://github.com/PlatformRelay/assent/',
    the value real cosign rejects. It now quotes the shipped
    '^https://github\.com/PlatformRelay/[Aa]ssent/' and carries an Amendment
    paragraph naming the cause: the Fulcio SAN carries the repo's post-rename
    casing (PlatformRelay/Assent from v0.2.0 on), cosign matches the regexp
    case-sensitively via Go RE2, so the lowercase value rejected every release
    from v0.2.0 on.
  * DoD gains the fixture clause; the stale `SECURITY.md:82–93` reference is
    refreshed to `SECURITY.md:80–98` at both sites (finding list and Reuse).

docs/decisions/decisions.md — D-153 records the substantive fact that until now
lived only in a commit body: SECURITY.md's published verification instructions
were BROKEN for every release since v0.2.0. An adopter following them on v0.2.0
or v0.3.0 got a verification failure on a genuine artifact, whose natural reading
is "this release was tampered with". Cause, the three decoded SANs, the rejected
alternatives ((?i), ship-broken-and-follow-up), the one-truth enforcement, and a
Revert line that states the revert re-breaks v0.2.0+.

openspec/specs/backlog.md — AUD2-F01 tracks SEC-03's twin,
hack/release/verify-artifacts.sh:124, with sizing and three traps. Recorded as a
found-in-flight follow-up OUTSIDE the AUD2 story table on purpose: D-152 records
AUD2 as five stories and AUD2-S05's exit gate dispositions S01–S04, so a sixth
row there would move that claim.

Not done, deliberately: the reviewer's optional suggestion to extend the pin with
the workflow path (`.../[Aa]ssent/\.github/workflows/release\.yaml@`). It is
hardening rather than a hole (exploiting the looseness needs repo write access),
and it is a code + fixture change in a round the review scoped to documentation.
Deferred to AUD2-F01, which touches the same pin.
@konih
konih merged commit 93e61f2 into main Aug 18, 2026
7 checks passed
@konih
konih deleted the lane/aud2-s03-integrate branch August 18, 2026 23:08
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