Skip to content

Commit 05ff5e1

Browse files
ci(scorecard-enforcer): split score-threshold from publish job (OSSF run-step ban) (#18)
## Summary This repo's `scorecard-enforcer.yml` has the OSSF publish-contract violation that hyperpolymath/standards#304 fixed in the canonical template. Every Scorecard run fails with: ``` webapp: scorecard job must only have steps with uses ``` ## Root cause OSSF's publish endpoint enforces a hard contract: the job that runs `ossf/scorecard-action` with `publish_results: true` must contain ONLY `uses:` steps. The pre-fix template's "Check minimum score" `run:` step in the same job fails the publish step and the whole workflow run. ## Fix Replace local file with the post-#304 standards template: - `scorecard` job: uses-only (now includes `upload-artifact` for SARIF hand-off) - `check-score` job: `needs: scorecard`, downloads artifact, runs the threshold gate ## Detection going forward Hypatia rule WF014 in hyperpolymath/hypatia#393 catches this pattern. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c1411d1 commit 05ff5e1

1 file changed

Lines changed: 51 additions & 10 deletions

File tree

.github/workflows/scorecard-enforcer.yml

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-License-Identifier: MPL-2.0-or-later
1+
# SPDX-License-Identifier: MPL-2.0
22
# Prevention workflow - runs OpenSSF Scorecard and fails on low scores
33
name: OpenSSF Scorecard Enforcer
44

@@ -9,37 +9,78 @@ on:
99
- cron: '0 6 * * 1' # Weekly on Monday
1010
workflow_dispatch:
1111

12-
permissions: read-all
12+
# Estate guardrail: cancel superseded runs so re-pushes / rebased PR
13+
# updates do not pile up queued runs against the shared account-wide
14+
# Actions concurrency pool. Applied only to read-only check workflows
15+
# (no publish/mutation), so cancelling a superseded run is always safe.
16+
concurrency:
17+
group: ${{ github.workflow }}-${{ github.ref }}
18+
cancel-in-progress: true
19+
20+
permissions:
21+
contents: read
1322

1423
jobs:
24+
# The OSSF Scorecard publish endpoint enforces a hard contract: the job that
25+
# runs `ossf/scorecard-action` with `publish_results: true` must contain
26+
# ONLY steps with `uses:` (no `run:` steps in the same job). If a `run:`
27+
# step is present, the publish step fails with:
28+
# "webapp: scorecard job must only have steps with uses"
29+
# (49 estate repos hit this; see ROADMAP audit 2026-05-30.)
30+
#
31+
# Fix: split the threshold check into a downstream job that depends on
32+
# `scorecard` and consumes the SARIF artifact. The `scorecard` job stays
33+
# uses-only; `check-score` is the gating job that emits the error.
1534
scorecard:
1635
runs-on: ubuntu-latest
1736
permissions:
37+
security-events: write
1838
id-token: write # For OIDC
19-
contents: read
2039
steps:
2140
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2241
with:
2342
persist-credentials: false
2443

2544
- name: Run Scorecard
26-
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
45+
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
46+
with:
47+
results_file: results.sarif
48+
results_format: sarif
49+
publish_results: true
50+
51+
- name: Upload SARIF
52+
uses: github/codeql-action/upload-sarif@c6f931105cb2c34c8f901cc885ba1e2e259cf745 # v4
53+
with:
54+
sarif_file: results.sarif
55+
56+
- name: Persist SARIF for downstream score-gate job
57+
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
58+
with:
59+
name: scorecard-results
60+
path: results.sarif
61+
retention-days: 1
62+
63+
check-score:
64+
needs: scorecard
65+
runs-on: ubuntu-latest
66+
permissions:
67+
contents: read
68+
steps:
69+
- name: Download SARIF from scorecard job
70+
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v5.0.0
2771
with:
28-
results_file: results.json
29-
results_format: json
30-
publish_results: false
72+
name: scorecard-results
3173

3274
- name: Check minimum score
3375
run: |
34-
# Parse score from results
35-
SCORE=$(jq -r '.score // 0' results.json 2>/dev/null || echo "0")
76+
SCORE=$(jq -r '.runs[0].tool.driver.properties.score // 0' results.sarif 2>/dev/null || echo "0")
3677
3778
echo "OpenSSF Scorecard Score: $SCORE"
3879
3980
# Minimum acceptable score (0-10 scale)
4081
MIN_SCORE=5
4182
42-
if ! awk -v score="$SCORE" -v min="$MIN_SCORE" 'BEGIN { exit(score >= min ? 0 : 1) }'; then
83+
if [ "$(echo "$SCORE < $MIN_SCORE" | bc -l)" = "1" ]; then
4384
echo "::error::Scorecard score $SCORE is below minimum $MIN_SCORE"
4485
exit 1
4586
fi

0 commit comments

Comments
 (0)