Repository navigation
fix(ci): clear the fixable Scorecard findings and add frontend coverage to Codecov - #3005
Conversation
The workflow granted contents: write and actions: write at the top level, which Scorecard's Token-Permissions check scores as a full ten point deduction each (checks/evaluation/permissions.go, reduceBy). Declare contents: read at the top and give the write scopes to the only job that force pushes development and dispatches ci.yml. Behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com>
security.yml installed semgrep with a bare pip install, the only finding behind Scorecard's Pinned-Dependencies score of 9. Pin semgrep and its transitive dependencies with pip-compile --generate-hashes, install with --require-hashes (which Scorecard treats as pinned, see isUnpinnedPipInstall in checks/raw/shell_download_validate.go), pin the interpreter the lockfile was compiled for, and let Dependabot recompile it. Also pin the two @latest installs in make security to fixed versions; Scorecard does not read the Makefile, but local and CI tooling should not drift apart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com>
The goreleaser job already attests the release archives, but the bundle only lives in GitHub's attestation store. Scorecard's Signed-Releases check reads release assets and counts provenance only for names ending in .intoto.jsonl (probes/releasesHaveProvenance), so every release scored 0. Upload the attest step's bundle as bindery_<version>.intoto.jsonl after goreleaser has published. The upload retries, uses --clobber so reruns are idempotent, and is continue-on-error with an ::error annotation: failing the job after the release is public would only skip deploy-prod and the Discord post. Document verifying against the file with --bundle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com>
GO-2026-5932 flags the deprecated golang.org/x/crypto/openpgp package at module level with no fixed version, so it matches every x/crypto release (v0.57.0 is current). Bindery imports no openpgp package and govulncheck reports no call. It was the one finding behind Scorecard's Vulnerabilities score of 9. Scorecard runs osv-scanner's DoScan without a config override (clients/osv.go), and osv-scanner loads osv-scanner.toml from the directory holding each lockfile, so a root level IgnoredVulns entry is honoured. Checked locally with osv-scanner v2.3.2, the version Scorecard vendors: the advisory is filtered and nothing else is reported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com>
.codecov.yml ignored web/**, so the badge only ever described Go code in internal/ and tools/. Collect v8 coverage from the existing vitest run (new test:coverage script, lcov written to web/coverage/ with repo relative paths) in validate-frontend and test, and upload it with flags: frontend and disable_search so it cannot pick up coverage.out. Project status moves into flag_management: each flag compares against its own base with the old auto target and 1% threshold, and carryforward covers a commit that uploads only one flag, so frontend coverage can never fail the backend status. Patch stays at 70%. Tests, test helpers, locale JSON and config files stay out; cmd/** stays ignored (25% wiring in cmd/bindery plus two separate services). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com>
Dependabot rewrites the attest-build-provenance uses: line (#2966); an id inserted right above it would conflict with that bump on merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com>
On a v* tag the test job gates the image and goreleaser, so run the Go and frontend suites there without coverage and skip both Codecov uploads. Coverage adds about a quarter to the runtime, and the tagged commit already reported coverage on main. Main and development keep coverage and uploads. Name the changelog fragment after the PR (#3005). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com>
There was a problem hiding this comment.
Clean CI/tooling PR. Nothing blocking; a couple of small notes.
Token-Permissions (dev-preview.yml:37-40): write scopes correctly scoped to the assemble job. Top-level contents: read is the right Scorecard-safe default.
Pinned-Dependencies (security.yml, semgrep.txt): --require-hashes with a pip-compile lockfile is the correct fix. SHA-pinned actions/setup-python, Dependabot pip entry — pattern is solid.
Vulnerabilities (osv-scanner.toml): ignore is well-documented with a reason and an implicit removal cue ("osv-scanner warns about unused ignores"). Appropriate given no fixed version and no reachable call path.
Codecov flags (.codecov.yml): project: false + per-flag flag_management is the right call to avoid a spurious regression on the first two-flag upload. carryforward: true handles partial-upload days correctly.
ci.yml upload retry loop (ci.yml:~648-658): Two minor issues in the bash:
for attempt in 1 2 3; do
if gh release upload ...; then exit 0; fi
echo "upload attempt ${attempt} failed; retrying" # ← misleading on attempt 3
sleep $((attempt * 10)) # ← sleeps 30s after the final failure
doneOn the third iteration the loop prints "retrying" when there is no retry, then waits 30 seconds before the ::error:: annotation fires. Not a correctness bug (the continue-on-error: true and three-attempt logic are fine), but worth tidying: gate the sleep on attempt < 3 or move it to the top of the loop body.
@vitest/coverage-v8 is not visible in the web/package.json diff, but npm run test:coverage --coverage requires it. The PR description's test run confirms it is already in devDependencies — just noting it so the next reader doesn't have to wonder.
— 🤖 Bindery triage bot (automated). Reply to correct me; a human will see it.
…gelog Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com>
* chore(release): assemble the v1.40.1 changelog Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com> * chore(release): add #2942, #2944, #2987 and #2989 to the v1.40.1 changelog Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com> * chore(release): add #2998 to the v1.40.1 changelog Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com> * chore(release): add #2665, #2926, #2937 and #3005 to the v1.40.1 changelog Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee <vavallee@protonmail.com> --------- Signed-off-by: vavallee <vavallee@protonmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Fixes the four OpenSSF Scorecard findings that can be fixed in code, and adds frontend coverage to Codecov so the badge stops describing Go
internal/only. Scoring rules below are from the Scorecard source (ossf/scorecardmain, which vendors osv-scanner v2.3.2), not from the docs.dev-preview.yml: top level is nowcontents: read;contents: write,actions: write,pull-requests: readmoved onto theassemblejobchecks/evaluation/permissions.go: a top levelcontents/actionswrite costs 10 points each (reduceBy). A job level write only deducts when the same file's top level is write (score to 0) or undeclared (minus 0.5)bindery_<version>.intoto.jsonlprobes/releasesHaveProvenance: only.intoto.jsonlcounts as provenance (10 per release);.sigstore.jsonwould only count as a signature (8). Score is the floor of the average over the last 5 releases, and older releases cannot be backfilled--require-hashesfrom a pip-compile lockfile; Python pinned to 3.12 via SHA pinnedactions/setup-python; Dependabotpipentry keeps it currentchecks/raw/shell_download_validate.goisUnpinnedPipInstall:--require-hashesmakes a pip install pinnedosv-scanner.tomlignores GO-2026-5932 with a reasonclients/osv.gocallsosvscanner.DoScanwith no config override, and osv-scanner loadsosv-scanner.tomlfrom the directory holdinggo.modNotes per item
Token-Permissions.
ci.ymldeploy-prodkeeps its job levelstatuses: write. On its own it is only a warning:ci.ymldeclarescontents: readat the top, so the job level case inpermissions.gonever deducts. Nothing about that job changed.Signed-Releases ordering and failure. The release is public before goreleaser runs (the CHANGELOG step creates it), so the upload only adds one asset afterwards. It retries three times, uses
--clobberso a rerun is idempotent, and iscontinue-on-errorwith an::errorannotation. Failing the goreleaser job there would skipdeploy-prodand the Discord post for a release whose binaries are already published and attested; the asset can be re-added by hand fromgh attestation download. The bundle is one JSON line (actions/attest writesJSON.stringify(bundle) + EOL), whichgh attestation verify --bundleaccepts. Theid: attestsits belowwith:so this merges cleanly with Dependabot's #2966 bump (checked withgit merge-tree);bundle-pathis still an output in v4.2.2.Vulnerabilities. No fix exists: the advisory is
introduced: 0with no fixed version, and x/crypto v0.57.0 is both what we use and@latest.go list -deps ./...shows no openpgp package and the pinned govulncheck reports "Your code is affected by 0 vulnerabilities".Pinned-Dependencies sweep. Workflows and the three Dockerfiles had no other unpinned
pip,npm install -g,go install @latestorcurl | sh. Themake securitytarget had two@latestinstalls; Scorecard does not read the Makefile, but they are pinned now too (gosec v2.29.0, govulncheck to the existingGOVULNCHECK_VERSION).Codecov.
validate-frontend(PRs) andtest(main and development) now runnpm run test:coverage(same suite plus v8 coverage) and uploadweb/coverage/lcov.infowithflags: frontendanddisable_search: true. lcov paths are repo relative (web/src/...) through the reporter'sprojectRoot, so no Codecov path fixing is needed. The single project status moved intoflag_management: each flag compares against its own base with the sameautotarget and 1% threshold, with carryforward, so a frontend upload cannot fail the backend status. Patch stays at 70%. Tests, test helpers, locale JSON and config files are excluded both in vitest and in.codecov.yml.cmd/**stays ignored:go test -cover ./cmd/...gives 25.2% forcmd/bindery(wiring), 0% and 38.7% for the two separate services. None of the Codecov statuses are required checks onmain.Removing
web/**from the ignore list also puts frontend lines intocodecov/patch: a PR that changes web code now needs 70% of its changed frontend lines covered, same target as Go.codecov/patchis not a required check, so this shows up as a red status, not a blocked merge.On a
v*tag thetestjob gates the image and goreleaser, so there it runs the plain Go and frontend suites with no coverage and no uploads (the tagged commit already reported on main). That keeps coverage's extra runtime and its timeout risk out of the release gate. Backend got the same split.The README badge is the shields.io overall number, so once main has a frontend upload it reports Go plus web combined. Codecov's Go metric still counts partially covered lines as misses; that was left alone on purpose.
What can only be verified at the next tag
bindery_1.40.1.intoto.jsonlto the v1.40.1 release, andgh attestation verify bindery_1.40.1_linux_amd64.tar.gz --repo vavallee/bindery --bundle bindery_1.40.1.intoto.jsonlpassing against it.docs/DEPLOYMENT.mdandSECURITY.mdneed adjusting).Checklist
docs/DEPLOYMENT.mdandSECURITY.mddescribe verifying with the bundlechangelog.d/3005-scorecard-and-coverage.md(Security)Test plan
actionlinton all workflows (1.7.12)goreleaser checkmake go-version-checkcd web && npm ci && npm run test:coverage: 105 files, 1220 tests pass, lcov written withweb/src/...paths. Frontend coverage 79.9% lines, 77.82% statements, 71.17% branches, 68.97% functionscd web && npm test,npm run build,npm run typecheck,npm run lint(0 errors).codecov.ymlvalidated withhttps://codecov.io/validatepip install --require-hashes -r .github/requirements/semgrep.txtinto a clean Python 3.12 venv,semgrep --versionprints 1.179.0semgrep.txton its own: no issuesOne run of the coverage suite had a single 5s test timeout (
HistoryPagepagination) while the box was at load 16 on 6 cores; it passed alone with coverage and on the rerun. Coverage adds about 25% to suite time locally (72s to 90s). Worth watching on the first CI runs.🤖 Generated with Claude Code
https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9