diff --git a/.github/workflows/scorecard-analysis.yml b/.github/workflows/scorecard-analysis.yml index 3856a4666..4a63510ef 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 cb05d1a07..c2be733b7 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 c3b8fa5db..4811a6d3f 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -408,7 +408,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 f4903c2f3..34e6a0328 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Pinned OpenSSF Scorecard to one immutable action SHA (`2d1146689b8cda280b9bc96326124645441f03bc`, v2.4.4) on both the PR visibility job and the default-branch analysis job so a Dependabot split cannot execute a second unreviewed control sphere (CWE-829). +- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context. - Parsed `opencode.jsonc` as JSONC (stripping `//` and `/* */` comments outside string literals) in the reasoning-effort guard and its contract tests, instead of raw `json.loads`, which rejected the file the moment it carried its first explanatory comment (added for the `contextual-orchestrator` provider block) with `Expecting property name enclosed in double quotes`. Comment markers inside string values, such as the `$schema` URL, are left untouched. - Download the pinned `uv` 0.12.1 exporter from the official GitHub Releases URL instead of `releases.astral.sh`, which now returns HTTP 403 and blocks org-wide OpenCode `coverage-evidence`. The SHA-256 pin is unchanged. The opener may follow one hop onto `release-assets.githubusercontent.com` or `objects.githubusercontent.com` and still rejects every other host, userinfo, non-HTTPS scheme, and nondefault port (ContextualWisdomLab/.github#1109). - Compared the trusted `uv` executable's post-install `--version` output against the real GitHub Releases build's full string, `uv 0.12.1 (x86_64-unknown-linux-gnu)`, instead of the bare `uv 0.12.1` the prior check required; the genuine release binary always prints the target triple, so every installation was failing the pin check immediately after the archive download itself was fixed (ContextualWisdomLab/.github#1109). diff --git a/docs/doctoring/scorecard-action-single-version.md b/docs/doctoring/scorecard-action-single-version.md new file mode 100644 index 000000000..5d3718a93 --- /dev/null +++ b/docs/doctoring/scorecard-action-single-version.md @@ -0,0 +1,25 @@ +# Scorecard action single-version pin + +## Incident and buyer impact + +Dependabot bumps `ossf/scorecard-action` one workflow at a time. If the +PR visibility job and the default-branch analysis job execute different +SHAs, a green Scorecard gate does not prove the scheduled posture scan +used the reviewed action. + +## Decision + +Pin both `scorecard-pr.yml` and `scorecard-analysis.yml` to +`2d1146689b8cda280b9bc96326124645441f03bc` (v2.4.4). A contract test +rejects a split. + +CWE-829 forbids including functionality from an untrusted or unreviewed +control sphere (MITRE, 2026). A second SHA is a second control sphere. + +## References + +MITRE. (2026). *CWE-829: Inclusion of functionality from untrusted +control sphere*. https://cwe.mitre.org/data/definitions/829.html + +OpenSSF. (n.d.). *Scorecard action*. GitHub. Retrieved August 13, 2026, +from https://github.com/ossf/scorecard-action diff --git a/tests/test_scorecard_action_pin_contract.py b/tests/test_scorecard_action_pin_contract.py new file mode 100644 index 000000000..97ec99ee7 --- /dev/null +++ b/tests/test_scorecard_action_pin_contract.py @@ -0,0 +1,35 @@ +"""Pin OpenSSF Scorecard to one immutable action SHA across workflows.""" + +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@([0-9a-f]{40}) # (v\d+\.\d+\.\d+)" +) + + +def test_scorecard_pr_and_analysis_share_one_action_sha() -> None: + """CWE-829: PR and scheduled Scorecard must execute one immutable SHA. + + A Dependabot bump that updates only one workflow would analyze PRs with + a different trusted action than the default-branch posture job. + """ + shas: set[str] = set() + tags: set[str] = set() + for filename in ("scorecard-pr.yml", "scorecard-analysis.yml"): + workflow = (REPO_ROOT / ".github/workflows" / filename).read_text( + encoding="utf-8" + ) + pins = _PIN.findall(workflow) + assert pins, f"{filename} has no pinned ossf/scorecard-action" + for sha, tag in pins: + shas.add(sha) + tags.add(tag) + + assert shas == {SCORECARD_SHA} + assert tags == {SCORECARD_TAG}