From 915494d3c27281ffaa7868c3d8efb7bdc7476db6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 03:49:10 +0900 Subject: [PATCH 1/4] chore(security): unify Scorecard Action v2.4.4 --- .github/workflows/scorecard-analysis.yml | 2 +- .github/workflows/scorecard-pr.yml | 2 +- .github/workflows/security-scan.yml | 2 +- CHANGELOG.md | 3 ++ .../scorecard-action-single-version.md | 33 +++++++++++++++++++ tests/test_scorecard_action_pin_contract.py | 31 +++++++++++++++++ 6 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 docs/doctoring/scorecard-action-single-version.md create mode 100644 tests/test_scorecard_action_pin_contract.py diff --git a/.github/workflows/scorecard-analysis.yml b/.github/workflows/scorecard-analysis.yml index 3856a46668..4a63510ef6 100644 --- a/.github/workflows/scorecard-analysis.yml +++ b/.github/workflows/scorecard-analysis.yml @@ -26,7 +26,7 @@ jobs: persist-credentials: false - name: Run analysis - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 with: results_file: results.sarif results_format: sarif diff --git a/.github/workflows/scorecard-pr.yml b/.github/workflows/scorecard-pr.yml index cb05d1a070..c2be733b72 100644 --- a/.github/workflows/scorecard-pr.yml +++ b/.github/workflows/scorecard-pr.yml @@ -46,7 +46,7 @@ jobs: persist-credentials: false - name: Run analysis - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 with: results_file: results.sarif results_format: sarif diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 29376269d4..83cb5e49e2 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -409,7 +409,7 @@ jobs: with: persist-credentials: false - name: Run Scorecard - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 with: results_file: results.sarif results_format: sarif diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b0ef8d447..4b96cfd16b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,9 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Aligned every central OpenSSF Scorecard Action use to the official v2.4.4 + commit so pull-request, scheduled, and combined security scans execute one + immutable, reviewed release. - Publish only the sanitized cumulative Strix report tree, avoiding a later copy of relative scanner output that could reintroduce known internal warning text into uploaded security evidence. diff --git a/docs/doctoring/scorecard-action-single-version.md b/docs/doctoring/scorecard-action-single-version.md new file mode 100644 index 0000000000..a44486e37c --- /dev/null +++ b/docs/doctoring/scorecard-action-single-version.md @@ -0,0 +1,33 @@ +# Scorecard Action single-version boundary + +## Incident boundary + +The central pull-request, scheduled, and combined security workflows all use +OpenSSF Scorecard, but dependency automation updates workflow references +independently. A partial bump can leave posture evidence produced by different +action releases even though the jobs appear to provide one control. + +## Decision + +Pin every central `ossf/scorecard-action` use to +`2d1146689b8cda280b9bc96326124645441f03bc`, the commit referenced by the +official signed v2.4.4 tag. The current release updates Scorecard to v5.5.0 and +records POST failures without failing the entire action (Open Source Security +Foundation, 2026). + +GitHub documents that a full commit SHA is unique and immutable and should be +verified against the action repository (GitHub, n.d.). A repository-wide +contract therefore parses every central workflow occurrence, rejects malformed +pins, and admits only the reviewed v2.4.4 SHA and tag. Workflow permissions, +events, arguments, SARIF semantics, thresholds, and fail-closed gates are +unchanged. + +## References + +GitHub. (n.d.). *Using pre-written building blocks in your workflow*. +Retrieved August 24, 2026, from +https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/find-and-customize-actions + +Open Source Security Foundation. (2026, July 23). *Scorecard Action v2.4.4* +[Software release]. +https://github.com/ossf/scorecard-action/releases/tag/v2.4.4 diff --git a/tests/test_scorecard_action_pin_contract.py b/tests/test_scorecard_action_pin_contract.py new file mode 100644 index 0000000000..7fc1e2e182 --- /dev/null +++ b/tests/test_scorecard_action_pin_contract.py @@ -0,0 +1,31 @@ +"""Keep every central OpenSSF Scorecard Action use on one reviewed release.""" + +from __future__ import annotations + +import re +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +SCORECARD_SHA = "2d1146689b8cda280b9bc96326124645441f03bc" +SCORECARD_TAG = "v2.4.4" +_PIN = re.compile( + r"ossf/scorecard-action@(?P[^\s]+)\s+#\s+(?Pv[^\s]+)" +) + + +def test_all_scorecard_actions_share_the_reviewed_current_release() -> None: + """Reject partial bumps, malformed refs, and stale Scorecard releases.""" + observed: set[tuple[str, str]] = set() + + for path in sorted((REPO_ROOT / ".github/workflows").glob("*.yml")): + for line_number, line in enumerate( + path.read_text(encoding="utf-8").splitlines(), start=1 + ): + if "ossf/scorecard-action@" not in line: + continue + match = _PIN.search(line) + assert match is not None, f"malformed Scorecard pin: {path}:{line_number}" + observed.add((match.group("sha"), match.group("tag"))) + + assert observed == {(SCORECARD_SHA, SCORECARD_TAG)} From 7db1d90c2f6b8f2c71fbfed404f276270ff9d8ab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 13:09:50 +0900 Subject: [PATCH 2/4] test(scorecard): cover yaml workflow pins --- tests/test_scorecard_action_pin_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_scorecard_action_pin_contract.py b/tests/test_scorecard_action_pin_contract.py index 7fc1e2e182..50bb839f79 100644 --- a/tests/test_scorecard_action_pin_contract.py +++ b/tests/test_scorecard_action_pin_contract.py @@ -18,7 +18,7 @@ def test_all_scorecard_actions_share_the_reviewed_current_release() -> None: """Reject partial bumps, malformed refs, and stale Scorecard releases.""" observed: set[tuple[str, str]] = set() - for path in sorted((REPO_ROOT / ".github/workflows").glob("*.yml")): + for path in sorted((REPO_ROOT / ".github/workflows").glob("*.y*ml")): for line_number, line in enumerate( path.read_text(encoding="utf-8").splitlines(), start=1 ): From c17d252096fe21cedbfdeef0233b79aeb1c0b669 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 13:10:48 +0900 Subject: [PATCH 3/4] test(scorecard): match action uses only --- tests/test_scorecard_action_pin_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_scorecard_action_pin_contract.py b/tests/test_scorecard_action_pin_contract.py index 50bb839f79..7bee0f392e 100644 --- a/tests/test_scorecard_action_pin_contract.py +++ b/tests/test_scorecard_action_pin_contract.py @@ -22,7 +22,7 @@ def test_all_scorecard_actions_share_the_reviewed_current_release() -> None: for line_number, line in enumerate( path.read_text(encoding="utf-8").splitlines(), start=1 ): - if "ossf/scorecard-action@" not in line: + if "uses:" not in line or "ossf/scorecard-action@" not in line: continue match = _PIN.search(line) assert match is not None, f"malformed Scorecard pin: {path}:{line_number}" From 61a8197a753e01d3275371d77b035ed44f6135eb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 03:58:36 +0900 Subject: [PATCH 4/4] fix(test): sync scorecard-action SHA expectation with v2.4.4 unification test_reusable_default_branch_scorecard_contract.py still asserted the old v2.4.3 pin (4eaacf05...), stale since this branch's own v2.4.4 unification touches this exact same action reference. Updated to the verified v2.4.4 commit (2d114668..., confirmed against ossf/scorecard-action's own tag ref). Co-Authored-By: Claude Sonnet 5 --- tests/test_reusable_default_branch_scorecard_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_reusable_default_branch_scorecard_contract.py b/tests/test_reusable_default_branch_scorecard_contract.py index a8f3a76750..f900ae493d 100644 --- a/tests/test_reusable_default_branch_scorecard_contract.py +++ b/tests/test_reusable_default_branch_scorecard_contract.py @@ -335,7 +335,7 @@ def test_scorecard_analysis_keeps_authoritative_sarif_boundaries() -> None: analysis_path = _step_path_by_name(workflow_contract, "Run analysis") assert workflow_contract[analysis_path + ("uses",)] == ( - "ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a" + "ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc" ) assert _mapping_contract( workflow_contract,