You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
pluggy upgrade calls assertAttestedByWorkflow (src/commands/upgrade.ts:239) to confirm a downloaded release binary has a Sigstore attestation in this repo's GitHub attestation store. The function checks two things:
At least one attestation exists for the binary's sha256.
The leaf certificate's SAN identifies the issuing OIDC identity as a workflow under https://github.com/{repo}/.github/workflows/.
It deliberately does not verify the Sigstore bundle cryptographically. From the inline comment:
We deliberately don't verify the Sigstore signature in-binary: that would mean shipping a Fulcio trust root and a full bundle verifier, a substantial amount of crypto code. The check above trusts GitHub's attestation API as a transport (same channel as the asset download), which is a meaningful improvement over no attestation at all without the implementation cost of a complete client-side Sigstore verifier.
The trade-off catches the realistic attacks (URL substitution, attestation-from-another-repo) but leaves gaps:
GitHub API as transport: a compromise of api.github.com (or its TLS chain) defeats both the asset download and the attestation lookup at once.
No Rekor SET check: we don't prove the attestation was published to the public transparency log within the cert's short validity window. A replayed attestation is in principle accepted as long as the SAN matches.
No Fulcio chain validation: a malformed or self-signed leaf cert that happens to include the expected SAN string in its subjectAltName would pass the current check.
Proposed solution
Replace the SAN-only check with full Sigstore bundle verification using @sigstore/verify:
Bundle the Fulcio public-good trust root and Rekor public key (as constants).
Pass the bundle returned by GET /repos/{owner}/{repo}/attestations/sha256:{hex} to the verifier.
Configure the verifier to require the same identity SAN we check today (https://github.com/{repo}/.github/workflows/*).
Keep the attestation-existence check; the API call is already the lookup mechanism.
Approximate shape:
import{Verifier,toSignedEntity}from"@sigstore/verify";constverifier=newVerifier({trustedRoot: FULCIO_TRUSTED_ROOT,// constants shipped in the binaryctlogThreshold: 1,tlogThreshold: 1,});verifier.verify(toSignedEntity(bundle),{certificateIdentities: [{issuer: "https://token.actions.githubusercontent.com",subjectAlternativeName: expectedIdentityPrefix}],artifactDigest: {algorithm: "sha256",hex: sha256Hex},});
This eliminates the GitHub-API-as-transport assumption and adds Rekor SET verification.
Alternatives considered
Status quo (SAN-only): cheap, catches the obvious attacks, depends on api.github.com integrity. Already in assertAttestedByWorkflow.
Pin the leaf cert's full DER: would catch some replay attacks but breaks every release that rotates the OIDC identity (every release). Not viable.
Pull from the npm sigstore CLI as a child process: removes the binary-size cost but introduces a runtime dependency on Node + npm + a separate package, defeating the single-file Bun-compiled distribution model.
Skip attestation entirely: regression on the security of the upgrade path.
actions/attest-build-provenance workflow already produces these attestations; no upstream change needed.
@sigstore/verify is the official Sigstore JS SDK: https://www.npmjs.com/package/@sigstore/verify. Bundle size adds ~400 KB to the compiled binary (rough estimate; worth measuring before deciding).
Rekor public key and Fulcio trust root rotate rarely (years). The maintenance is real but bounded.
Problem
pluggy upgradecallsassertAttestedByWorkflow(src/commands/upgrade.ts:239) to confirm a downloaded release binary has a Sigstore attestation in this repo's GitHub attestation store. The function checks two things:https://github.com/{repo}/.github/workflows/.It deliberately does not verify the Sigstore bundle cryptographically. From the inline comment:
The trade-off catches the realistic attacks (URL substitution, attestation-from-another-repo) but leaves gaps:
api.github.com(or its TLS chain) defeats both the asset download and the attestation lookup at once.subjectAltNamewould pass the current check.Proposed solution
Replace the SAN-only check with full Sigstore bundle verification using
@sigstore/verify:GET /repos/{owner}/{repo}/attestations/sha256:{hex}to the verifier.https://github.com/{repo}/.github/workflows/*).Approximate shape:
This eliminates the GitHub-API-as-transport assumption and adds Rekor SET verification.
Alternatives considered
api.github.comintegrity. Already inassertAttestedByWorkflow.sigstoreCLI as a child process: removes the binary-size cost but introduces a runtime dependency on Node + npm + a separate package, defeating the single-file Bun-compiled distribution model.Additional context
src/commands/upgrade.ts:214-237.actions/attest-build-provenanceworkflow already produces these attestations; no upstream change needed.@sigstore/verifyis the official Sigstore JS SDK: https://www.npmjs.com/package/@sigstore/verify. Bundle size adds ~400 KB to the compiled binary (rough estimate; worth measuring before deciding).ch99q/review-code-conventionsPR (refactor: align CLI with quality conventions, add why/outdated/audit, polish UX #28) review thread, where the current trade-off was flagged as honest-but-skippable.