diff --git a/.github/workflows/codeql-pr.yml b/.github/workflows/codeql-pr.yml index 240e7139d..36e07be44 100644 --- a/.github/workflows/codeql-pr.yml +++ b/.github/workflows/codeql-pr.yml @@ -90,13 +90,13 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Initialize CodeQL - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: category: "/language:${{ matrix.language }}" upload: false @@ -197,13 +197,13 @@ jobs: ref: ${{ format('refs/pull/{0}/merge', github.event.pull_request.number) }} - name: Initialize CodeQL - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: category: "/language:${{ matrix.language }}-merge" upload: false diff --git a/.github/workflows/python-security.yml b/.github/workflows/python-security.yml index cd51cf81a..739615dc0 100644 --- a/.github/workflows/python-security.yml +++ b/.github/workflows/python-security.yml @@ -191,7 +191,7 @@ jobs: if: always() && hashFiles('bandit-results.sarif') != '' # The explicit gate below still fails on every Medium+ Bandit result. continue-on-error: true - uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: bandit-results.sarif category: bandit diff --git a/.github/workflows/sast-semgrep.yml b/.github/workflows/sast-semgrep.yml index b2e264988..3b2ab95f5 100644 --- a/.github/workflows/sast-semgrep.yml +++ b/.github/workflows/sast-semgrep.yml @@ -103,7 +103,7 @@ jobs: - name: Upload Semgrep SARIF to code scanning if: always() && hashFiles('semgrep-results.sarif') != '' continue-on-error: true - uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: semgrep-results.sarif category: semgrep diff --git a/.github/workflows/scheduled-security-scan.yml b/.github/workflows/scheduled-security-scan.yml index dbf6b764f..8ecba0734 100644 --- a/.github/workflows/scheduled-security-scan.yml +++ b/.github/workflows/scheduled-security-scan.yml @@ -90,13 +90,13 @@ jobs: with: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 + uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis continue-on-error: true - uses: github/codeql-action/analyze@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 + uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: category: "/language:${{ matrix.language }}-scheduled" @@ -131,7 +131,7 @@ jobs: - name: Upload Trivy SARIF to code scanning if: always() && hashFiles('trivy-results.sarif') != '' continue-on-error: true - uses: github/codeql-action/upload-sarif@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: trivy-results.sarif category: trivy-fs-scheduled diff --git a/.github/workflows/scorecard-analysis.yml b/.github/workflows/scorecard-analysis.yml index 3856a4666..d6c4db372 100644 --- a/.github/workflows/scorecard-analysis.yml +++ b/.github/workflows/scorecard-analysis.yml @@ -65,6 +65,6 @@ jobs: # Scorecard posture is preserved in its SARIF-generation log; an # installation upload quota outage must not fail the default branch. continue-on-error: true - uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: results.sarif diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml index 1fab22a48..d97c5e3d5 100644 --- a/.github/workflows/secret-scan.yml +++ b/.github/workflows/secret-scan.yml @@ -130,7 +130,7 @@ jobs: - name: Upload gitleaks SARIF to code scanning if: always() && hashFiles('gitleaks-results.upload.sarif') != '' continue-on-error: true - uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: gitleaks-results.upload.sarif category: gitleaks diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 29376269d..9cecc3591 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -226,7 +226,7 @@ jobs: # The reporter above is the vulnerability gate. Preserve an upload # quota failure in this step's log without reclassifying it as a CVE. continue-on-error: true - uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: results.sarif # results.sarif is produced after checkout of the pull request head. @@ -384,7 +384,7 @@ jobs: if: always() && hashFiles('trivy-results.sarif') != '' # The parser above fails on every fixable Medium+ finding independently. continue-on-error: true - uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: trivy-results.sarif category: trivy-fs @@ -458,7 +458,7 @@ jobs: id: upload_scorecard_sarif # Scorecard is soft repository-posture evidence; upload quota is external. continue-on-error: true - uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: results.sarif category: scorecard diff --git a/CHANGELOG.md b/CHANGELOG.md index 1630c32d4..73ae1a84e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,9 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Aligned every central `github/codeql-action` `init`, `analyze`, and + `upload-sarif` use to the official v4.37.7 commit so analyzer and evidence + upload steps run one immutable, reviewed release across security workflows. - 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/codeql-action-single-version.md b/docs/doctoring/codeql-action-single-version.md new file mode 100644 index 000000000..e91453cf7 --- /dev/null +++ b/docs/doctoring/codeql-action-single-version.md @@ -0,0 +1,33 @@ +# CodeQL Action single-version boundary + +## Incident boundary + +The central security workflows referenced three CodeQL Action releases: +v4.37.0 for pull-request analysis, v4.37.5 for scheduled analysis, and v4.37.4 +for most SARIF uploads. The open v4.37.6 alignment (#918) predated the official +v4.37.7 release, while the v4.37.7 dependency update (#1107) covered only the +two workflows containing `init` and `analyze` steps. Five upload-only workflows +would therefore have remained on a different reviewed artifact. + +## Decision + +Pin every central `github/codeql-action/{init,analyze,upload-sarif}` use to +`ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd`, the commit referenced by the +official annotated v4.37.7 tag. GitHub documents that a full commit SHA is +unique and immutable and must be verified against the action repository +(GitHub, n.d.). The v4.37.7 release updates the default CodeQL bundle to +v2.26.3 (GitHub, 2026). + +The change does not alter workflow permissions, event triggers, SARIF paths, +finding thresholds, or fail-closed gates. A repository-wide contract parses +every central workflow occurrence, rejects malformed pins, requires all three +CodeQL Action entry points, and admits only the reviewed SHA and release tag. + +## References + +GitHub. (2026, August 13). *CodeQL Action v4.37.7* [Software release]. +https://github.com/github/codeql-action/releases/tag/v4.37.7 + +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 diff --git a/tests/test_codeql_pr_workflow_contract.py b/tests/test_codeql_pr_workflow_contract.py index 813385b23..0c2f6d271 100644 --- a/tests/test_codeql_pr_workflow_contract.py +++ b/tests/test_codeql_pr_workflow_contract.py @@ -57,6 +57,31 @@ def test_codeql_action_steps_use_one_version_per_workflow() -> None: assert len(refs) == 1, f"{filename} mixes CodeQL action refs: {sorted(refs)}" +def test_all_codeql_actions_share_the_reviewed_current_release() -> None: + """Pin every central CodeQL action use to the reviewed v4.37.7 commit.""" + expected = ("ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd", "v4.37.7") + pattern = re.compile( + r"github/codeql-action/(?Pinit|analyze|upload-sarif)@" + r"(?P[^\s]+)\s+#\s+(?Pv[^\s]+)" + ) + actions: set[str] = set() + observed: set[tuple[str, str]] = set() + + 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 + ): + if "uses:" not in line or "github/codeql-action/" not in line: + continue + match = pattern.search(line) + assert match is not None, f"malformed CodeQL pin: {path}:{line_number}" + actions.add(match.group("action")) + observed.add((match.group("sha"), match.group("tag"))) + + assert actions == {"init", "analyze", "upload-sarif"} + assert observed == {expected} + + def test_codeql_sarif_gate_logs_and_fails_only_unsuppressed_medium_plus( tmp_path: Path, ) -> None: