Repository navigation
feat(score): the score push is signed keyless, the report names its CLI version, config file hash and run - #584
Open
thomasboni wants to merge 5 commits into
Open
thomasboni wants to merge 5 commits into
thomasboni wants to merge 5 commits into
Conversation
thomasboni
force-pushed
the
feat/signed-score-push
branch
from
October 31, 2026 00:40
5e6eae2 to
bc92e41
Compare
… JSON report Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155zD4xr5A8b2CUxqdKMB3U
…ctions Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155zD4xr5A8b2CUxqdKMB3U
thomasboni
force-pushed
the
feat/signed-score-push
branch
from
October 31, 2026 00:49
bc92e41 to
64fbc06
Compare
thomasboni
force-pushed
the
feat/signed-score-push
branch
from
October 31, 2026 00:57
64fbc06 to
a92ea46
Compare
…ilure only warns Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155zD4xr5A8b2CUxqdKMB3U
The bytes the CLI signs and posts are the publish payload (buildPublishPayload, which drops analyzedCiConfig), not the --output file, so nothing on disk let a user or an e2e verify the Rekor entry against what was pushed. When PLUMBER_SCORE_PAYLOAD_OUT names a non-empty path, maybePushScore now writes the payload verbatim there (0o600, overwriting) right before the POST, whether or not signing succeeded. A write error is one scoreWarn line and never stops the push. Env only, no flag: this is a verification aid, not a CI input. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155zD4xr5A8b2CUxqdKMB3U
…CORE_PAYLOAD_OUT Document the GitHub Actions signing path (Sigstore id-token, Fulcio certificate, Rekor log entry), the Plumber-Sigstore-Bundle request header (ignored by a self-hosted score service that does not know it), the printed Rekor index line, and PLUMBER_SCORE_PAYLOAD_OUT for verifying a push against its Rekor entry. Document cliVersion, configFileSha256 and ci in the JSON output schema reference. The link to the official organizations page of the docs site is not written yet, per the monorepo spec; refer to the page without a URL. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155zD4xr5A8b2CUxqdKMB3U
thomasboni
force-pushed
the
feat/signed-score-push
branch
from
October 31, 2026 01:18
a92ea46 to
3c3097c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
cliVersion(the build's version, the Go module pseudo-version for an unstamped build from a clone),configFileSha256(the sha256 of the.plumber.yamlbytes the run used, empty when the built-in default applies) andci(on GitHub Actions:runId,runAttempt,runUrl).plumber analyze --score-pushsigns the exact bytes it posts: a Fulcio certificate for the job's OIDC identity and a Rekor transparency log entry.Plumber-Sigstore-Bundlerequest header. A signing failure only warns (report not signed (...); pushed without a Sigstore bundle) and the push goes on unsigned.✓ Plumber Score published: ..., a signed push prints✓ report signed (Rekor index N).PLUMBER_SCORE_PAYLOAD_OUT=<path>writes the exact pushed bytes, so a reader can hash them against the Rekor entry.https://score.getplumber.io, or whenPLUMBER_SCORE_SIGN=truenames a self-hosted score service that verifies the bundle. Anywhere else the signer is never built: no warning, no id-token mint, no TUF fetch, no Rekor entry, and the push is the same request as before.github.com/sigstore/sigstore-go v1.3.0, pinned under the current Go floor (go 1.26.0stays; v1.4.0 requires Go 1.27). Binary size: 51,064,598 to 61,790,749 bytes, a delta of 10,726,151 bytes (about 10.2 MiB). Two indirect bumps keep the scanners clean:sigstore/rekor v1.5.4(its PGP support moved to ProtonMail/go-crypto, which dropsgolang.org/x/crypto/openpgp, GO-2026-5932; v1.5.5 would need Go 1.27) andgo-jose/v4 v4.1.6(GHSA-mgcc-cww5-fp2q and two Mediums).PLUMBER_SCORE_PAYLOAD_OUTandPLUMBER_SCORE_SIGN; docs/GITHUB_ISSUES.md documents the provenance keys.Why
The hosted score service is getting an organization trust page: a public, organization-level attestation of Plumber scores that holds without any server-side re-run, following the OpenSSF Scorecard model. The organization's own Actions run signs its result keyless; the service verifies the Rekor entry and the Fulcio certificate, which bind the exact result bytes to the job's repository, ref, commit and workflow, instead of trusting the pushed body. The provenance keys tell a reader which CLI version, which configuration file and which run produced a score.
Behaviour unchanged
--score-push skippednote).--platform): nothing is signed, the platform push is unchanged (nil bundle).The CHANGELOG is generated by semantic-release and was not edited.
Verification
make lint(0 issues),make deadcode(only the pre-existingconfiguration/schema.goConfigSchemasfinding),go vet ./...,go test ./... -count=1andgovulncheck ./...all pass on the head;grype --only-fixedfinds nothing in the built binary..plumber.yaml(see "Final review fixes").✓ Plumber Score published: https://score.getplumber.io/github.com/getplumber/plumber-lab-githuband✓ report signed (Rekor index 3606093578). Then sha256 of thePLUMBER_SCORE_PAYLOAD_OUTbytes = the Rekor entry'shashedrekordhash =5664b065dc8c4e2f74e40b0807dbd216de38269a60ccbf58cb460edd392977a9. The report carriedcliVersion 0.0.0-20261031001606-5e6eae2316f6, an emptyconfigFileSha256(no.plumber.yamlin the lab) and theciblock with run id 41149439727.https://github.com/getplumber/plumber-lab-github/.github/workflows/signed-score-push-e2e.yml@refs/heads/trust-e2ehttps://token.actions.githubusercontent.comhttps://github.com/getplumber/plumber-lab-githubrefs/heads/trust-e2e613c43bd4b5ccf516b9532cd48ace1666fbeba430b60cc82...abb9, checked in the job) ran through the Plumber component in platform mode on a self-managed GitLab: pipeline 6591, job 8514 succeeded. The log shows✓ Results pushed to the platformfollowed by the platform's verdict (A 92 / 100), and noreport signedornot signedline.Final review fixes
PLUMBER_SCORE_SIGN=trueopts a self-hosted service in); a push elsewhere never builds the signer and sends no header. Tests:TestMaybePushScore_SelfHostedEndpointNeverSigns,TestMaybePushScore_SelfHostedOptInSigns,TestScoreSigningWanted,TestSignScorePayload_HostedEndpointSigns.TestMaindefaults the signer seam tonoSigner. Thecmdpackage passes with only loopback networking (unshare -rn,loup), and a fullgo test ./...no longer recreates~/.sigstore/root.Sigstore bundle too large (...), pushed without it) and the push goes on. Test:TestMaybePushScore_OversizeBundleDroppedWithWarning(70 KiB stub bundle).scoreSignTimeoutis an overall cap:Signruns in a goroutine and the caller selects on the context, so it returns at the deadline even when an inner leg ignores the context. Test:TestSignScorePayload_TimeoutIsAnOverallCap(a signer that honors the context and one that does not, 50ms deadline)..plumber.yaml: run https://github.com/getplumber/plumber-lab-github/actions/runs/41154951511 (CLI0.0.0-20261031011525-3c3097ce3244) printed✓ Plumber Score published: ...and✓ report signed (Rekor index 3607100154); thePLUMBER_SCORE_PAYLOAD_OUTsha256 equals the Rekor hash,45f3e2fefa20b0fc1d93fc6d5930018b2d3afed1ebfb3d819edc72ba1f0058c2; the report'sconfigFileSha256dcf95764c96c4cbb171243cd402310e1c715774b1ef986539e6524f9b7d256eeequalssha256sum .plumber.yamlin the lab commit. The attempt just before it (run 41154708555) met a slow public Rekor (a plain GET took 15.3s at the time): it printedreport not signed (... rekor.sigstore.dev ... context deadline exceeded); pushed without a Sigstore bundleand the score was still published, the warning path working as designed.🤖 Generated with Claude Code
https://claude.ai/code/session_0155zD4xr5A8b2CUxqdKMB3U