Skip to content

Commit e639338

Browse files
ci: extract scorecard run steps into composite actions (#54)
The Hypatia `scorecard_publish_with_run_step` rule is file-level: any run: step in a workflow that publishes OpenSSF Scorecard results trips it, so the earlier job-split did not clear it. Move all run logic out of the scorecard-publishing workflow into local composite actions, leaving scorecard-enforcer.yml with no run: steps at all: - .github/actions/scorecard-gate — the minimum-score gate (consumes the SARIF). - .github/actions/repo-security-checks — SECURITY.md + unpinned-action checks. The publish job (which holds the OIDC id-token) and the whole workflow file now contain only `uses:` steps; the score-gate and check-critical jobs invoke the composite actions after checkout. Claude-Session: https://claude.ai/code/session_01AqMopxUsgu78rg5fhWBUkk Co-authored-by: Claude <noreply@anthropic.com>
1 parent fc56afa commit e639338

3 files changed

Lines changed: 62 additions & 31 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
name: Repository Security Checks
4+
description: Assert SECURITY.md exists and warn on unpinned actions in workflows.
5+
runs:
6+
using: composite
7+
steps:
8+
- name: Check SECURITY.md exists
9+
shell: bash
10+
run: |
11+
if [ ! -f "SECURITY.md" ]; then
12+
echo "::error::SECURITY.md is required"
13+
exit 1
14+
fi
15+
- name: Check for pinned dependencies
16+
shell: bash
17+
run: |
18+
unpinned=$(grep -r "uses:.*@v[0-9]" .github/workflows/*.yml 2>/dev/null | grep -v "#" | head -5 || true)
19+
if [ -n "$unpinned" ]; then
20+
echo "::warning::Found unpinned actions:"
21+
echo "$unpinned"
22+
fi
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
name: Scorecard Score Gate
4+
description: Fail when the OpenSSF Scorecard score is below the minimum.
5+
inputs:
6+
sarif:
7+
description: Path to the Scorecard SARIF results file.
8+
required: false
9+
default: results.sarif
10+
min-score:
11+
description: Minimum acceptable score (0-10).
12+
required: false
13+
default: "5"
14+
runs:
15+
using: composite
16+
steps:
17+
- name: Check minimum score
18+
shell: bash
19+
run: |
20+
SCORE=$(jq -r '.runs[0].tool.driver.properties.score // 0' "${{ inputs.sarif }}" 2>/dev/null || echo "0")
21+
echo "OpenSSF Scorecard Score: $SCORE"
22+
MIN_SCORE="${{ inputs.min-score }}"
23+
if [ "$(echo "$SCORE < $MIN_SCORE" | bc -l)" = "1" ]; then
24+
echo "::error::Scorecard score $SCORE is below minimum $MIN_SCORE"
25+
exit 1
26+
fi

.github/workflows/scorecard-enforcer.yml

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -43,50 +43,33 @@ jobs:
4343
name: scorecard-sarif
4444
path: results.sarif
4545
retention-days: 1
46-
# The score gate runs in a separate job so the publish job (which holds the
47-
# OIDC id-token) contains no custom run steps.
46+
# The score gate runs the check via a local composite action so this
47+
# scorecard-publishing workflow file contains no run: steps (the publish job
48+
# holds the OIDC id-token; run logic lives in .github/actions/*).
4849
score-gate:
4950
needs: scorecard
5051
runs-on: ubuntu-latest
5152
timeout-minutes: 5
5253
permissions:
5354
contents: read
5455
steps:
56+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
57+
with:
58+
persist-credentials: false
5559
- name: Download results artifact
5660
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
5761
with:
5862
name: scorecard-sarif
59-
- name: Check minimum score
60-
run: |
61-
# Parse score from results
62-
SCORE=$(jq -r '.runs[0].tool.driver.properties.score // 0' results.sarif 2>/dev/null || echo "0")
63-
64-
echo "OpenSSF Scorecard Score: $SCORE"
65-
66-
# Minimum acceptable score (0-10 scale)
67-
MIN_SCORE=5
68-
69-
if [ "$(echo "$SCORE < $MIN_SCORE" | bc -l)" = "1" ]; then
70-
echo "::error::Scorecard score $SCORE is below minimum $MIN_SCORE"
71-
exit 1
72-
fi
73-
# Check specific high-priority items
63+
- name: Gate on minimum score
64+
uses: ./.github/actions/scorecard-gate
65+
with:
66+
sarif: results.sarif
67+
min-score: "5"
68+
# Check specific high-priority items (run logic in a local composite action).
7469
check-critical:
7570
runs-on: ubuntu-latest
7671
timeout-minutes: 15
7772
steps:
7873
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
79-
- name: Check SECURITY.md exists
80-
run: |
81-
if [ ! -f "SECURITY.md" ]; then
82-
echo "::error::SECURITY.md is required"
83-
exit 1
84-
fi
85-
- name: Check for pinned dependencies
86-
run: |
87-
# Check workflows for unpinned actions
88-
unpinned=$(grep -r "uses:.*@v[0-9]" .github/workflows/*.yml 2>/dev/null | grep -v "#" | head -5 || true)
89-
if [ -n "$unpinned" ]; then
90-
echo "::warning::Found unpinned actions:"
91-
echo "$unpinned"
92-
fi
74+
- name: Repository security checks
75+
uses: ./.github/actions/repo-security-checks

0 commit comments

Comments
 (0)