Skip to content

Commit c9a893a

Browse files
ci: scorecard 50/50 startup_failure + scorecard-enforcer webapp 400 + governance SHA-pin (#264)
## Summary Three foundational CI bugs surfaced by direct audit; each fixed at the correct layer (in-repo, not estate-wide). ### `scorecard-enforcer.yml` — two-job split Score itself was succeeding (7.9 measured), but the OSSF Scorecard webapp HTTP-400'd the signed-result POST: > workflow verification failed: scorecard job must only have steps with `uses`, see https://github.com/ossf/scorecard-action#workflow-restrictions because the `scorecard` job had a `Check minimum score` step that used `run:`. The webapp's workflow verifier rejects mixed-shape jobs when `publish_results: true` triggers the signed-publish path. **Fix**: split the single job into two: - `scorecard` (uses-only): runs scorecard-action with publish, uploads SARIF, saves `results.sarif` as actions artifact. - `enforce-min-score` (needs: scorecard): downloads the artifact and runs the bash min-score gate. `run:` steps are fine here because this job does NOT call scorecard-action. Action SHA pins verified via `gh api repos/<org>/<action>/commits/<sha>` (`actions/upload-artifact@4cec3d8` + `actions/download-artifact@cc20338`). ### `scorecard.yml` — align with documented caller pattern **50/50 ever runs** of this workflow are `startup_failure` (workflow has never successfully run). Caller diverged from the reusable's documented caller-example in 4 ways: 1. Missing `concurrency:` group — re-pushes piled up superseded runs. 2. `branch_protection_rule:` trigger present (unused; complicates startup). 3. `secrets: inherit` against a reusable with no `secrets:` block. 4. `permissions: read-all` instead of explicit `contents: read`. **Fix**: now matches the reusable's header example verbatim. SHA pin unchanged. ### `governance.yml` — SHA-pin floating `@main` ref Per memory hook `feedback_gh_run_rerun_caches_floating_ref_resolution`, floating refs cache resolution unpredictably across re-runs. Pinned to `d3f799c639420ac68ac7ed83d2166b625b4b73f0` (current `hyperpolymath/standards` main); `governance-reusable.yml` verified present at that SHA. ## Test plan - [ ] Scorecard run after merge: confirm publish step no longer HTTP-400s; signed result reaches OSSF webapp - [ ] Scorecards run after merge: workflow actually starts (not startup_failure) and produces a run - [ ] Governance run after merge: no resolution flap 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 223be67 commit c9a893a

3 files changed

Lines changed: 58 additions & 9 deletions

File tree

.github/workflows/governance.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ permissions:
2222

2323
jobs:
2424
governance:
25-
uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@main
25+
uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@d3f799c639420ac68ac7ed83d2166b625b4b73f0

.github/workflows/scorecard-enforcer.yml

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
# SPDX-License-Identifier: PMPL-1.0-or-later
2-
# Prevention workflow - runs OpenSSF Scorecard and fails on low scores
2+
# Prevention workflow - runs OpenSSF Scorecard and fails on low scores.
3+
#
4+
# Split into TWO jobs to satisfy the OSSF Scorecard webapp's workflow
5+
# restriction:
6+
# "scorecard job must only have steps with `uses`"
7+
# (https://github.com/ossf/scorecard-action#workflow-restrictions).
8+
#
9+
# When the publishing job contains a `run:` step, the webapp rejects
10+
# the signed-result POST with HTTP 400 "workflow verification failed".
11+
# That regression had this workflow `failure`-state ever since
12+
# `publish_results: true` was added — the score *was* computed, but
13+
# the webapp publish step failed and propagated to job-level failure.
14+
#
15+
# Job 1 (`scorecard`): uses-only steps. Computes the score, publishes
16+
# signed results to the OSSF webapp, uploads SARIF to GitHub code
17+
# scanning, and saves results.json as an artifact for Job 2.
18+
#
19+
# Job 2 (`enforce-min-score`): downloads the artifact and runs the
20+
# minimum-score gate. May contain `run:` steps freely — it does not
21+
# call scorecard-action.
22+
323
name: OpenSSF Scorecard Enforcer
424

525
on:
@@ -43,14 +63,30 @@ jobs:
4363
with:
4464
sarif_file: results.sarif
4565

66+
- name: Upload results artifact for min-score gate
67+
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
68+
with:
69+
name: scorecard-results
70+
path: results.sarif
71+
retention-days: 7
72+
73+
enforce-min-score:
74+
needs: scorecard
75+
runs-on: ubuntu-latest
76+
steps:
77+
- name: Download results artifact
78+
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
79+
with:
80+
name: scorecard-results
81+
4682
- name: Check minimum score
4783
run: |
48-
# Parse score from results
84+
# Parse score from SARIF results.
4985
SCORE=$(jq -r '.runs[0].tool.driver.properties.score // 0' results.sarif 2>/dev/null || echo "0")
5086
5187
echo "OpenSSF Scorecard Score: $SCORE"
5288
53-
# Minimum acceptable score (0-10 scale)
89+
# Minimum acceptable score (0-10 scale).
5490
MIN_SCORE=5
5591
5692
if [ "$(echo "$SCORE < $MIN_SCORE" | bc -l)" = "1" ]; then

.github/workflows/scorecard.yml

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
11
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
# Aligned with the documented caller pattern in
3+
# hyperpolymath/standards .github/workflows/scorecard-reusable.yml.
4+
# Previously this workflow was startup_failure-on-every-run (50/50)
5+
# because it diverged from the reusable's documented caller shape:
6+
# - missing `concurrency:` group (re-pushes piled up superseded runs)
7+
# - `branch_protection_rule:` trigger (unused; complicates startup)
8+
# - `secrets: inherit` against a reusable with no `secrets:` block
9+
# - `permissions: read-all` instead of explicit `contents: read`
10+
# Pattern now matches the reusable's header example verbatim.
211
name: Scorecards supply-chain security
312

413
on:
5-
branch_protection_rule:
6-
schedule:
7-
- cron: '23 4 * * 1'
814
push:
915
branches: [main]
16+
schedule:
17+
- cron: '23 4 * * 1'
18+
workflow_dispatch:
19+
20+
concurrency:
21+
group: ${{ github.workflow }}-${{ github.ref }}
22+
cancel-in-progress: true
1023

11-
permissions: read-all
24+
permissions:
25+
contents: read
1226

1327
jobs:
1428
analysis:
1529
permissions:
1630
security-events: write
1731
id-token: write
1832
uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@e0caf11508a3989574713c78f5f444f2ce5e33ef
19-
secrets: inherit

0 commit comments

Comments
 (0)