From efd875b328601930969a98c37f9076e3d44363ea Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 16:50:27 +0900 Subject: [PATCH 01/24] fix(security): keep every CodeQL Action phase on one revision Rebuild the PR from protected develop with only the atomic CodeQL lifecycle update, its test-first contract, doctoring record, and changelog entry. Remove unrelated PDF.js and npm lockfile drift from this branch. --- .github/workflows/codeql.yml | 6 +- .github/workflows/ossf-scorecard.yml | 2 +- .github/workflows/trivy.yml | 2 +- CHANGELOG.md | 4 ++ .../codeql-action-atomic-revision.md | 61 +++++++++++++++++++ .../test_codeql_action_revision_contract.py | 54 ++++++++++++++++ 6 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 docs/doctoring/codeql-action-atomic-revision.md create mode 100644 services/analysis-engine/tests/test_codeql_action_revision_contract.py diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 27c5b540f..ee9b6019a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -32,8 +32,8 @@ jobs: - python steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + - uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 with: languages: ${{ matrix.language }} - - uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 - - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + - uses: github/codeql-action/autobuild@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + - uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 2a4b6eaa9..a3c287789 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -79,6 +79,6 @@ jobs: python3 trusted-scorecard-scripts/scripts/checks/normalize_scorecard_sarif.py scorecard-sarif/results.sarif normalized-scorecard-results.sarif - - uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 peeled commit; SHA pinning retained as supply-chain attack mitigation. + - uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6; immutable SHA pinning retained as supply-chain attack mitigation. with: sarif_file: normalized-scorecard-results.sarif diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index d79ec32e1..cd417efba 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -48,7 +48,7 @@ jobs: skip-dirs: 'services/analysis-engine/.venv' trivyignores: ./.trivyignore - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 peeled commit; SHA pinning retained as supply-chain attack mitigation. + uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6; immutable SHA pinning retained as supply-chain attack mitigation. if: always() with: sarif_file: trivy-results.sarif diff --git a/CHANGELOG.md b/CHANGELOG.md index eea696893..105f06d26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - Display the analyzed song tempo (BPM) as a badge in the rehearsal workspace. - 각 합주 역할(Role)별 개인 연습 진행도를 0~100% 범위로 기록 및 시각화할 수 있는 연습 진척도(`practiceProgress`) 트래커 기능 추가. UI 컨트롤(슬라이더 및 +/- 버튼)과 한/영 다국어 지원 포함. +### Changed + +- Updated every CodeQL Action phase and SARIF uploader together to the reviewed immutable `v4.37.6` revision, with a regression contract that prevents mixed `init`, `autobuild`, `analyze`, and `upload-sarif` versions. + ## [0.1.3] - 2026-04-29 ### Fixed diff --git a/docs/doctoring/codeql-action-atomic-revision.md b/docs/doctoring/codeql-action-atomic-revision.md new file mode 100644 index 000000000..61312f4fa --- /dev/null +++ b/docs/doctoring/codeql-action-atomic-revision.md @@ -0,0 +1,61 @@ +# Atomic CodeQL Action revision policy + +## Decision + +BandScope treats the CodeQL Action lifecycle as one supply-chain dependency. Every checked-in reference to `github/codeql-action/init`, `autobuild`, `analyze`, and `upload-sarif` must use the same reviewed full-length commit SHA and matching release annotation. + +The current reviewed revision is CodeQL Action `v4.37.6` at commit `5595ccaf912efad79be6eef63a5619ff05969be3`. + +GitHub documents `init` as the phase that initializes CodeQL, `autobuild` as the optional automatic build phase, and `analyze` as the phase that finalizes the database, runs queries, and uploads results. `upload-sarif` publishes SARIF generated by other tools. These phases exchange state and therefore move together in this repository rather than through independent dependency pull requests. + +## Threat and compatibility boundary + +A full commit SHA is the immutable execution identity. Tags remain useful release labels, but they are not accepted as the workflow execution reference. GitHub identifies a full-length commit SHA as the strongest immutable action reference and supports organization policy requiring that form. + +Independently updating one phase can leave the repository with mixed JavaScript bundles, CodeQL CLI expectations, feature flags, or SARIF transport behavior. Even when each individual release is valid, the mixed lifecycle has not been reviewed or tested as a unit. The atomic policy prevents both persistent drift and the transient mixed state that can occur when several Dependabot pull requests merge at different times. + +```mermaid +flowchart LR + A[CodeQL init] --> B[CodeQL autobuild] + B --> C[CodeQL analyze] + C --> D[GitHub code scanning] + E[Trivy and Scorecard SARIF] --> F[CodeQL upload-sarif] + R[One reviewed release SHA] --> A + R --> B + R --> C + R --> F +``` + +The change does not alter workflow triggers, language selection, build behavior, SARIF paths, permissions, or failure handling. It changes only the immutable CodeQL Action implementation identity and version comments. + +## Verification contract + +`services/analysis-engine/tests/test_codeql_action_revision_contract.py` scans every workflow and fails unless: + +1. all CodeQL Action phases use one exact reviewed SHA; +2. every reference carries the matching `v4.37.6` annotation; and +3. `codeql.yml` keeps `init`, `autobuild`, and `analyze` on that same revision. + +Repository CI, CodeQL, SAST, dependency/security scans, SBOM generation, central coverage evidence, automated review, independent approval, and branch protection must all validate the final exact head. Results from split predecessor pull requests are not transferable. + +## Update procedure + +1. Identify the newest supported CodeQL Action v4 release from the upstream GitHub repository. +2. Verify the tag resolves to the intended upstream commit and inspect the release notes. +3. Add or update the contract expectation first and observe the RED failure against the old revision. +4. Update every `init`, `autobuild`, `analyze`, and `upload-sarif` reference in one branch. +5. Run the focused contract, workflow/static checks, and the complete repository gates. +6. Merge only after exact-current-head review and branch protection succeed without bypass. +7. Close split dependency pull requests as superseded; do not reuse their checks or approvals. + +## Rollback + +Rollback restores the previously accepted full-length SHA across every CodeQL Action phase in one reviewed commit. A partial rollback is prohibited. After rollback, rerun the same exact-head security, quality, SARIF publication, and review gates before accepting the branch. + +## References + +GitHub. (2026). *CodeQL Action v4.37.6* [Software release]. https://github.com/github/codeql-action/releases/tag/v4.37.6 + +GitHub. (n.d.). *CodeQL code scanning for compiled languages*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration/codeql-for-compiled-languages + +GitHub. (n.d.). *Secure use reference*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/reference/security/secure-use diff --git a/services/analysis-engine/tests/test_codeql_action_revision_contract.py b/services/analysis-engine/tests/test_codeql_action_revision_contract.py new file mode 100644 index 000000000..1f9bc2d6f --- /dev/null +++ b/services/analysis-engine/tests/test_codeql_action_revision_contract.py @@ -0,0 +1,54 @@ +"""Supply-chain contracts for a coherent GitHub CodeQL Action revision.""" + +from __future__ import annotations + +import re +from pathlib import Path + +_REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +_WORKFLOW_ROOT = _REPOSITORY_ROOT / ".github" / "workflows" +_EXPECTED_CODEQL_ACTION_SHA = "5595ccaf912efad79be6eef63a5619ff05969be3" +_EXPECTED_CODEQL_ACTION_VERSION = "v4.37.6" +_CODEQL_ACTION_REFERENCE = re.compile( + r"github/codeql-action/(init|autobuild|analyze|upload-sarif)@([0-9a-f]{40})([^\n]*)" +) + + +def _codeql_action_references() -> list[tuple[Path, str, str, str]]: + """Return every pinned CodeQL Action reference from checked-in workflows.""" + references: list[tuple[Path, str, str, str]] = [] + for workflow_path in sorted(_WORKFLOW_ROOT.glob("*.y*ml")): + workflow_text = workflow_path.read_text(encoding="utf-8") + for action_name, revision_sha, suffix in _CODEQL_ACTION_REFERENCE.findall(workflow_text): + references.append((workflow_path, action_name, revision_sha, suffix.strip())) + return references + + +def test_every_codeql_action_step_uses_the_same_reviewed_revision() -> None: + """Prevent independently updated phases from creating mixed CodeQL runtimes.""" + references = _codeql_action_references() + + assert references + assert {revision_sha for _, _, revision_sha, _ in references} == { + _EXPECTED_CODEQL_ACTION_SHA + } + assert all( + f"# {_EXPECTED_CODEQL_ACTION_VERSION}" in suffix + for _, _, _, suffix in references + ) + + +def test_analysis_workflow_keeps_init_autobuild_and_analyze_atomic() -> None: + """Require the analysis lifecycle to move as one immutable dependency unit.""" + workflow_path = _WORKFLOW_ROOT / "codeql.yml" + workflow_text = workflow_path.read_text(encoding="utf-8") + references = { + action_name: revision_sha + for action_name, revision_sha, _suffix in _CODEQL_ACTION_REFERENCE.findall(workflow_text) + } + + assert references == { + "init": _EXPECTED_CODEQL_ACTION_SHA, + "autobuild": _EXPECTED_CODEQL_ACTION_SHA, + "analyze": _EXPECTED_CODEQL_ACTION_SHA, + } From ec2f03051a20edabeb820ad40b7812f84f9fbba2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 16:55:55 +0900 Subject: [PATCH 02/24] style(test): format CodeQL revision contract --- .../tests/test_codeql_action_revision_contract.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/analysis-engine/tests/test_codeql_action_revision_contract.py b/services/analysis-engine/tests/test_codeql_action_revision_contract.py index 1f9bc2d6f..997e51326 100644 --- a/services/analysis-engine/tests/test_codeql_action_revision_contract.py +++ b/services/analysis-engine/tests/test_codeql_action_revision_contract.py @@ -19,8 +19,10 @@ def _codeql_action_references() -> list[tuple[Path, str, str, str]]: references: list[tuple[Path, str, str, str]] = [] for workflow_path in sorted(_WORKFLOW_ROOT.glob("*.y*ml")): workflow_text = workflow_path.read_text(encoding="utf-8") - for action_name, revision_sha, suffix in _CODEQL_ACTION_REFERENCE.findall(workflow_text): - references.append((workflow_path, action_name, revision_sha, suffix.strip())) + matches = _CODEQL_ACTION_REFERENCE.findall(workflow_text) + for action_name, revision_sha, suffix in matches: + reference = (workflow_path, action_name, revision_sha, suffix.strip()) + references.append(reference) return references @@ -32,10 +34,8 @@ def test_every_codeql_action_step_uses_the_same_reviewed_revision() -> None: assert {revision_sha for _, _, revision_sha, _ in references} == { _EXPECTED_CODEQL_ACTION_SHA } - assert all( - f"# {_EXPECTED_CODEQL_ACTION_VERSION}" in suffix - for _, _, _, suffix in references - ) + expected_version = f"# {_EXPECTED_CODEQL_ACTION_VERSION}" + assert all(expected_version in suffix for _, _, _, suffix in references) def test_analysis_workflow_keeps_init_autobuild_and_analyze_atomic() -> None: From c453203d132ab2216331a6ab9b7727eb7f9f7cc0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 17:04:39 +0900 Subject: [PATCH 03/24] ci: add one-shot Ruff formatter for PR 780 --- .github/workflows/repair-pr-780-format.yml | 56 ++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/repair-pr-780-format.yml diff --git a/.github/workflows/repair-pr-780-format.yml b/.github/workflows/repair-pr-780-format.yml new file mode 100644 index 000000000..025e07760 --- /dev/null +++ b/.github/workflows/repair-pr-780-format.yml @@ -0,0 +1,56 @@ +name: repair-pr-780-format + +on: + push: + branches: + - fix/codeql-action-consistency-v4-37-6 + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: repair-pr-780-format + cancel-in-progress: false + +jobs: + format: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Check out the exact feature branch + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: fix/codeql-action-consistency-v4-37-6 + fetch-depth: 1 + + - name: Install pinned uv + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v6.7.0 + with: + version: "0.8.6" + enable-cache: false + + - name: Format only the failing contract test + shell: bash + run: | + set -euo pipefail + uvx ruff@0.15.5 format \ + services/analysis-engine/tests/test_codeql_action_revision_contract.py + changed="$(git diff --name-only)" + test "$changed" = "services/analysis-engine/tests/test_codeql_action_revision_contract.py" + uvx ruff@0.15.5 format --check \ + services/analysis-engine/tests/test_codeql_action_revision_contract.py + + - name: Commit the formatter result and remove this one-shot workflow + shell: bash + run: | + set -euo pipefail + rm .github/workflows/repair-pr-780-format.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add \ + services/analysis-engine/tests/test_codeql_action_revision_contract.py \ + .github/workflows/repair-pr-780-format.yml + git diff --cached --check + git commit -m "style(test): apply canonical Ruff formatting" + git push origin HEAD:fix/codeql-action-consistency-v4-37-6 From 1348c6d29f4f4e10a2cfb843c75fa9e3f0bde519 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 08:06:00 +0000 Subject: [PATCH 04/24] style(test): apply canonical Ruff formatting --- .github/workflows/repair-pr-780-format.yml | 56 ------------------- .../test_codeql_action_revision_contract.py | 4 +- 2 files changed, 1 insertion(+), 59 deletions(-) delete mode 100644 .github/workflows/repair-pr-780-format.yml diff --git a/.github/workflows/repair-pr-780-format.yml b/.github/workflows/repair-pr-780-format.yml deleted file mode 100644 index 025e07760..000000000 --- a/.github/workflows/repair-pr-780-format.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: repair-pr-780-format - -on: - push: - branches: - - fix/codeql-action-consistency-v4-37-6 - workflow_dispatch: - -permissions: - contents: write - -concurrency: - group: repair-pr-780-format - cancel-in-progress: false - -jobs: - format: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Check out the exact feature branch - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: fix/codeql-action-consistency-v4-37-6 - fetch-depth: 1 - - - name: Install pinned uv - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v6.7.0 - with: - version: "0.8.6" - enable-cache: false - - - name: Format only the failing contract test - shell: bash - run: | - set -euo pipefail - uvx ruff@0.15.5 format \ - services/analysis-engine/tests/test_codeql_action_revision_contract.py - changed="$(git diff --name-only)" - test "$changed" = "services/analysis-engine/tests/test_codeql_action_revision_contract.py" - uvx ruff@0.15.5 format --check \ - services/analysis-engine/tests/test_codeql_action_revision_contract.py - - - name: Commit the formatter result and remove this one-shot workflow - shell: bash - run: | - set -euo pipefail - rm .github/workflows/repair-pr-780-format.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add \ - services/analysis-engine/tests/test_codeql_action_revision_contract.py \ - .github/workflows/repair-pr-780-format.yml - git diff --cached --check - git commit -m "style(test): apply canonical Ruff formatting" - git push origin HEAD:fix/codeql-action-consistency-v4-37-6 diff --git a/services/analysis-engine/tests/test_codeql_action_revision_contract.py b/services/analysis-engine/tests/test_codeql_action_revision_contract.py index 997e51326..8a82481c4 100644 --- a/services/analysis-engine/tests/test_codeql_action_revision_contract.py +++ b/services/analysis-engine/tests/test_codeql_action_revision_contract.py @@ -31,9 +31,7 @@ def test_every_codeql_action_step_uses_the_same_reviewed_revision() -> None: references = _codeql_action_references() assert references - assert {revision_sha for _, _, revision_sha, _ in references} == { - _EXPECTED_CODEQL_ACTION_SHA - } + assert {revision_sha for _, _, revision_sha, _ in references} == {_EXPECTED_CODEQL_ACTION_SHA} expected_version = f"# {_EXPECTED_CODEQL_ACTION_VERSION}" assert all(expected_version in suffix for _, _, _, suffix in references) From 0a6ae2b4d749d473f3b0a451bed1a6a58c876080 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 17:10:06 +0900 Subject: [PATCH 05/24] ci: retrigger exact-head validation after canonical Ruff formatting From f4f40a160c9711a1cc05454f5306872c4f98fe5d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 08:16:16 +0900 Subject: [PATCH 06/24] fix(security): advance atomic CodeQL revision to v4.37.7 Update every CodeQL lifecycle phase and SARIF uploader to the verified upstream v4.37.7 commit, refresh the regression contract, changelog, and doctoring, and record the v2.26.3 bundle update. Preserve immutable SHA pinning and the existing atomic-version policy. --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/ossf-scorecard.yml | 2 +- .github/workflows/trivy.yml | 2 +- CHANGELOG.md | 2 +- docs/doctoring/codeql-action-atomic-revision.md | 12 +++++++----- .../tests/test_codeql_action_revision_contract.py | 4 ++-- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ee9b6019a..1ea17ad12 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -32,8 +32,8 @@ jobs: - python steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + - uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: languages: ${{ matrix.language }} - - uses: github/codeql-action/autobuild@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 - - uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + - uses: github/codeql-action/autobuild@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + - uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index a3c287789..0da083df9 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -79,6 +79,6 @@ jobs: python3 trusted-scorecard-scripts/scripts/checks/normalize_scorecard_sarif.py scorecard-sarif/results.sarif normalized-scorecard-results.sarif - - uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6; immutable SHA pinning retained as supply-chain attack mitigation. + - uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7; immutable SHA pinning retained as supply-chain attack mitigation. with: sarif_file: normalized-scorecard-results.sarif diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index cd417efba..9e5b97c9d 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -48,7 +48,7 @@ jobs: skip-dirs: 'services/analysis-engine/.venv' trivyignores: ./.trivyignore - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6; immutable SHA pinning retained as supply-chain attack mitigation. + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7; immutable SHA pinning retained as supply-chain attack mitigation. if: always() with: sarif_file: trivy-results.sarif diff --git a/CHANGELOG.md b/CHANGELOG.md index 105f06d26..b7807db3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ ### Changed -- Updated every CodeQL Action phase and SARIF uploader together to the reviewed immutable `v4.37.6` revision, with a regression contract that prevents mixed `init`, `autobuild`, `analyze`, and `upload-sarif` versions. +- Updated every CodeQL Action phase and SARIF uploader together to the reviewed immutable `v4.37.7` revision, with a regression contract that prevents mixed `init`, `autobuild`, `analyze`, and `upload-sarif` versions. ## [0.1.3] - 2026-04-29 diff --git a/docs/doctoring/codeql-action-atomic-revision.md b/docs/doctoring/codeql-action-atomic-revision.md index 61312f4fa..25fa941f8 100644 --- a/docs/doctoring/codeql-action-atomic-revision.md +++ b/docs/doctoring/codeql-action-atomic-revision.md @@ -4,7 +4,7 @@ BandScope treats the CodeQL Action lifecycle as one supply-chain dependency. Every checked-in reference to `github/codeql-action/init`, `autobuild`, `analyze`, and `upload-sarif` must use the same reviewed full-length commit SHA and matching release annotation. -The current reviewed revision is CodeQL Action `v4.37.6` at commit `5595ccaf912efad79be6eef63a5619ff05969be3`. +The current reviewed revision is CodeQL Action `v4.37.7` at commit `ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd`. The upstream release was published on August 13, 2026 and advances the default CodeQL bundle to `2.26.3`. GitHub documents `init` as the phase that initializes CodeQL, `autobuild` as the optional automatic build phase, and `analyze` as the phase that finalizes the database, runs queries, and uploads results. `upload-sarif` publishes SARIF generated by other tools. These phases exchange state and therefore move together in this repository rather than through independent dependency pull requests. @@ -33,7 +33,7 @@ The change does not alter workflow triggers, language selection, build behavior, `services/analysis-engine/tests/test_codeql_action_revision_contract.py` scans every workflow and fails unless: 1. all CodeQL Action phases use one exact reviewed SHA; -2. every reference carries the matching `v4.37.6` annotation; and +2. every reference carries the matching `v4.37.7` annotation; and 3. `codeql.yml` keeps `init`, `autobuild`, and `analyze` on that same revision. Repository CI, CodeQL, SAST, dependency/security scans, SBOM generation, central coverage evidence, automated review, independent approval, and branch protection must all validate the final exact head. Results from split predecessor pull requests are not transferable. @@ -54,8 +54,10 @@ Rollback restores the previously accepted full-length SHA across every CodeQL Ac ## References -GitHub. (2026). *CodeQL Action v4.37.6* [Software release]. https://github.com/github/codeql-action/releases/tag/v4.37.6 +GitHub. (2026). *CodeQL Action v4.37.7* [Software release]. https://github.com/github/codeql-action/releases/tag/v4.37.7 -GitHub. (n.d.). *CodeQL code scanning for compiled languages*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration/codeql-for-compiled-languages +GitHub. (2026). *CodeQL Bundle v2.26.3* [Software release]. https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3 -GitHub. (n.d.). *Secure use reference*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/reference/security/secure-use +GitHub. (n.d.). *CodeQL code scanning for compiled languages*. GitHub Docs. Retrieved August 15, 2026, from https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration/codeql-for-compiled-languages + +GitHub. (n.d.). *Secure use reference*. GitHub Docs. Retrieved August 15, 2026, from https://docs.github.com/en/actions/reference/security/secure-use diff --git a/services/analysis-engine/tests/test_codeql_action_revision_contract.py b/services/analysis-engine/tests/test_codeql_action_revision_contract.py index 8a82481c4..d86d5b22a 100644 --- a/services/analysis-engine/tests/test_codeql_action_revision_contract.py +++ b/services/analysis-engine/tests/test_codeql_action_revision_contract.py @@ -7,8 +7,8 @@ _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] _WORKFLOW_ROOT = _REPOSITORY_ROOT / ".github" / "workflows" -_EXPECTED_CODEQL_ACTION_SHA = "5595ccaf912efad79be6eef63a5619ff05969be3" -_EXPECTED_CODEQL_ACTION_VERSION = "v4.37.6" +_EXPECTED_CODEQL_ACTION_SHA = "ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd" +_EXPECTED_CODEQL_ACTION_VERSION = "v4.37.7" _CODEQL_ACTION_REFERENCE = re.compile( r"github/codeql-action/(init|autobuild|analyze|upload-sarif)@([0-9a-f]{40})([^\n]*)" ) From 0e57921de5e8f0ef8b66a739c3d4110709753a60 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 07:06:41 +0900 Subject: [PATCH 07/24] test(codeql): detect mutable action references --- .../tests/test_codeql_action_revision_contract.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/services/analysis-engine/tests/test_codeql_action_revision_contract.py b/services/analysis-engine/tests/test_codeql_action_revision_contract.py index d86d5b22a..5ec75adf8 100644 --- a/services/analysis-engine/tests/test_codeql_action_revision_contract.py +++ b/services/analysis-engine/tests/test_codeql_action_revision_contract.py @@ -26,6 +26,15 @@ def _codeql_action_references() -> list[tuple[Path, str, str, str]]: return references +def test_reference_scanner_detects_non_sha_codeql_references() -> None: + """Mutable CodeQL refs must be visible to the guard instead of being skipped.""" + workflow_line = "uses: github/codeql-action/init@v4 # mutable reference" + + matches = _CODEQL_ACTION_REFERENCE.findall(workflow_line) + + assert matches == [("init", "v4", " # mutable reference")] + + def test_every_codeql_action_step_uses_the_same_reviewed_revision() -> None: """Prevent independently updated phases from creating mixed CodeQL runtimes.""" references = _codeql_action_references() From 30d92d54648976f3047b47ed86d8d43ee3407bcd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 07:06:52 +0900 Subject: [PATCH 08/24] fix(codeql): scan mutable refs before enforcing SHA pin --- .../tests/test_codeql_action_revision_contract.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/services/analysis-engine/tests/test_codeql_action_revision_contract.py b/services/analysis-engine/tests/test_codeql_action_revision_contract.py index 5ec75adf8..9add54ae4 100644 --- a/services/analysis-engine/tests/test_codeql_action_revision_contract.py +++ b/services/analysis-engine/tests/test_codeql_action_revision_contract.py @@ -10,18 +10,18 @@ _EXPECTED_CODEQL_ACTION_SHA = "ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd" _EXPECTED_CODEQL_ACTION_VERSION = "v4.37.7" _CODEQL_ACTION_REFERENCE = re.compile( - r"github/codeql-action/(init|autobuild|analyze|upload-sarif)@([0-9a-f]{40})([^\n]*)" + r"github/codeql-action/(init|autobuild|analyze|upload-sarif)@([^\s#]+)([^\n]*)" ) def _codeql_action_references() -> list[tuple[Path, str, str, str]]: - """Return every pinned CodeQL Action reference from checked-in workflows.""" + """Return every CodeQL Action reference from checked-in workflows.""" references: list[tuple[Path, str, str, str]] = [] for workflow_path in sorted(_WORKFLOW_ROOT.glob("*.y*ml")): workflow_text = workflow_path.read_text(encoding="utf-8") matches = _CODEQL_ACTION_REFERENCE.findall(workflow_text) - for action_name, revision_sha, suffix in matches: - reference = (workflow_path, action_name, revision_sha, suffix.strip()) + for action_name, revision_ref, suffix in matches: + reference = (workflow_path, action_name, revision_ref, suffix.strip()) references.append(reference) return references @@ -40,7 +40,7 @@ def test_every_codeql_action_step_uses_the_same_reviewed_revision() -> None: references = _codeql_action_references() assert references - assert {revision_sha for _, _, revision_sha, _ in references} == {_EXPECTED_CODEQL_ACTION_SHA} + assert {revision_ref for _, _, revision_ref, _ in references} == {_EXPECTED_CODEQL_ACTION_SHA} expected_version = f"# {_EXPECTED_CODEQL_ACTION_VERSION}" assert all(expected_version in suffix for _, _, _, suffix in references) @@ -50,8 +50,8 @@ def test_analysis_workflow_keeps_init_autobuild_and_analyze_atomic() -> None: workflow_path = _WORKFLOW_ROOT / "codeql.yml" workflow_text = workflow_path.read_text(encoding="utf-8") references = { - action_name: revision_sha - for action_name, revision_sha, _suffix in _CODEQL_ACTION_REFERENCE.findall(workflow_text) + action_name: revision_ref + for action_name, revision_ref, _suffix in _CODEQL_ACTION_REFERENCE.findall(workflow_text) } assert references == { From 738495ca9d425fb34a12e76c0614eda356e68f3a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 07:07:07 +0900 Subject: [PATCH 09/24] docs(codeql): document mutable-ref detection --- docs/doctoring/codeql-action-atomic-revision.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/doctoring/codeql-action-atomic-revision.md b/docs/doctoring/codeql-action-atomic-revision.md index 25fa941f8..dba5e8e5f 100644 --- a/docs/doctoring/codeql-action-atomic-revision.md +++ b/docs/doctoring/codeql-action-atomic-revision.md @@ -36,6 +36,8 @@ The change does not alter workflow triggers, language selection, build behavior, 2. every reference carries the matching `v4.37.7` annotation; and 3. `codeql.yml` keeps `init`, `autobuild`, and `analyze` on that same revision. +The scanner intentionally recognizes mutable and malformed revision tokens such as `@v4` before enforcing the exact-SHA invariant. A tag-style reference therefore becomes a failing value instead of disappearing from the evidence set because it did not already look like a 40-character SHA. + Repository CI, CodeQL, SAST, dependency/security scans, SBOM generation, central coverage evidence, automated review, independent approval, and branch protection must all validate the final exact head. Results from split predecessor pull requests are not transferable. ## Update procedure From c43bf95bc0cb0fff952c3ef46bcb15c31fd4bed5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 11:15:21 -0700 Subject: [PATCH 10/24] test(codeql): require atomic v4.37.8 revision --- .../tests/test_codeql_action_revision_contract.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/analysis-engine/tests/test_codeql_action_revision_contract.py b/services/analysis-engine/tests/test_codeql_action_revision_contract.py index 9add54ae4..cdca297df 100644 --- a/services/analysis-engine/tests/test_codeql_action_revision_contract.py +++ b/services/analysis-engine/tests/test_codeql_action_revision_contract.py @@ -7,8 +7,8 @@ _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] _WORKFLOW_ROOT = _REPOSITORY_ROOT / ".github" / "workflows" -_EXPECTED_CODEQL_ACTION_SHA = "ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd" -_EXPECTED_CODEQL_ACTION_VERSION = "v4.37.7" +_EXPECTED_CODEQL_ACTION_SHA = "db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28" +_EXPECTED_CODEQL_ACTION_VERSION = "v4.37.8" _CODEQL_ACTION_REFERENCE = re.compile( r"github/codeql-action/(init|autobuild|analyze|upload-sarif)@([^\s#]+)([^\n]*)" ) @@ -58,4 +58,4 @@ def test_analysis_workflow_keeps_init_autobuild_and_analyze_atomic() -> None: "init": _EXPECTED_CODEQL_ACTION_SHA, "autobuild": _EXPECTED_CODEQL_ACTION_SHA, "analyze": _EXPECTED_CODEQL_ACTION_SHA, - } + } \ No newline at end of file From 695546852e4e56c191171ffd6a044f196d34fbfd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 11:15:48 -0700 Subject: [PATCH 11/24] build(codeql): advance analysis phases to v4.37.8 --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1ea17ad12..8440aad8d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -32,8 +32,8 @@ jobs: - python steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + - uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 with: languages: ${{ matrix.language }} - - uses: github/codeql-action/autobuild@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 - - uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + - uses: github/codeql-action/autobuild@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + - uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 \ No newline at end of file From 81bcc60fa64a306f628225f98ed7c836b36bbdbd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 11:16:24 -0700 Subject: [PATCH 12/24] build(codeql): advance Scorecard upload to v4.37.8 --- .github/workflows/ossf-scorecard.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 0da083df9..9ec1220d1 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -79,6 +79,6 @@ jobs: python3 trusted-scorecard-scripts/scripts/checks/normalize_scorecard_sarif.py scorecard-sarif/results.sarif normalized-scorecard-results.sarif - - uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7; immutable SHA pinning retained as supply-chain attack mitigation. + - uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8; immutable SHA pinning retained as supply-chain attack mitigation. with: - sarif_file: normalized-scorecard-results.sarif + sarif_file: normalized-scorecard-results.sarif \ No newline at end of file From 708cb3d33f21547ec6f2611b4b643e94759df21f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 11:16:47 -0700 Subject: [PATCH 13/24] build(codeql): advance Trivy SARIF upload to v4.37.8 --- .github/workflows/trivy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 9e5b97c9d..33044efb5 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -48,7 +48,7 @@ jobs: skip-dirs: 'services/analysis-engine/.venv' trivyignores: ./.trivyignore - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7; immutable SHA pinning retained as supply-chain attack mitigation. + uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8; immutable SHA pinning retained as supply-chain attack mitigation. if: always() with: - sarif_file: trivy-results.sarif + sarif_file: trivy-results.sarif \ No newline at end of file From b07c0cad4a8e405d82cb5f51c229030897fae80a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 11:17:42 -0700 Subject: [PATCH 14/24] docs(codeql): record v4.37.8 atomic revision --- docs/doctoring/codeql-action-atomic-revision.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/doctoring/codeql-action-atomic-revision.md b/docs/doctoring/codeql-action-atomic-revision.md index dba5e8e5f..9ce557a3c 100644 --- a/docs/doctoring/codeql-action-atomic-revision.md +++ b/docs/doctoring/codeql-action-atomic-revision.md @@ -4,7 +4,7 @@ BandScope treats the CodeQL Action lifecycle as one supply-chain dependency. Every checked-in reference to `github/codeql-action/init`, `autobuild`, `analyze`, and `upload-sarif` must use the same reviewed full-length commit SHA and matching release annotation. -The current reviewed revision is CodeQL Action `v4.37.7` at commit `ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd`. The upstream release was published on August 13, 2026 and advances the default CodeQL bundle to `2.26.3`. +The current reviewed revision is CodeQL Action `v4.37.8` at commit `db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28`. The upstream annotated tag object `37f2634a92ba38a0926ef79a0748ac8ae7d95ab2` resolves to that commit. The release was published on August 21, 2026 and reports no user-facing changes; it retains the v4.37.7-era default CodeQL bundle `2.26.3`. GitHub documents `init` as the phase that initializes CodeQL, `autobuild` as the optional automatic build phase, and `analyze` as the phase that finalizes the database, runs queries, and uploads results. `upload-sarif` publishes SARIF generated by other tools. These phases exchange state and therefore move together in this repository rather than through independent dependency pull requests. @@ -33,7 +33,7 @@ The change does not alter workflow triggers, language selection, build behavior, `services/analysis-engine/tests/test_codeql_action_revision_contract.py` scans every workflow and fails unless: 1. all CodeQL Action phases use one exact reviewed SHA; -2. every reference carries the matching `v4.37.7` annotation; and +2. every reference carries the matching `v4.37.8` annotation; and 3. `codeql.yml` keeps `init`, `autobuild`, and `analyze` on that same revision. The scanner intentionally recognizes mutable and malformed revision tokens such as `@v4` before enforcing the exact-SHA invariant. A tag-style reference therefore becomes a failing value instead of disappearing from the evidence set because it did not already look like a 40-character SHA. @@ -56,10 +56,10 @@ Rollback restores the previously accepted full-length SHA across every CodeQL Ac ## References -GitHub. (2026). *CodeQL Action v4.37.7* [Software release]. https://github.com/github/codeql-action/releases/tag/v4.37.7 +GitHub. (2026). *CodeQL Action v4.37.8* [Software release]. https://github.com/github/codeql-action/releases/tag/v4.37.8 GitHub. (2026). *CodeQL Bundle v2.26.3* [Software release]. https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3 -GitHub. (n.d.). *CodeQL code scanning for compiled languages*. GitHub Docs. Retrieved August 15, 2026, from https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration/codeql-for-compiled-languages +GitHub. (n.d.). *CodeQL code scanning for compiled languages*. GitHub Docs. Retrieved August 25, 2026, from https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration/codeql-for-compiled-languages -GitHub. (n.d.). *Secure use reference*. GitHub Docs. Retrieved August 15, 2026, from https://docs.github.com/en/actions/reference/security/secure-use +GitHub. (n.d.). *Secure use reference*. GitHub Docs. Retrieved August 25, 2026, from https://docs.github.com/en/actions/reference/security/secure-use \ No newline at end of file From b860c4960083d3abf0ffe2682b2a75f0418f42c0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 11:18:39 -0700 Subject: [PATCH 15/24] docs(changelog): record CodeQL v4.37.8 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7807db3a..a2121acea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ ### Changed -- Updated every CodeQL Action phase and SARIF uploader together to the reviewed immutable `v4.37.7` revision, with a regression contract that prevents mixed `init`, `autobuild`, `analyze`, and `upload-sarif` versions. +- Updated every CodeQL Action phase and SARIF uploader together to the reviewed immutable `v4.37.8` revision, with a regression contract that prevents mixed `init`, `autobuild`, `analyze`, and `upload-sarif` versions. ## [0.1.3] - 2026-04-29 @@ -69,4 +69,4 @@ - `ChordsFeature` (코드 분석) 화면에서 각 파트(Role)의 `transpositionPlan`(이조/조옮김 계획)을 표시하는 기능을 추가했습니다. - `RangesFeature` (음역대 분석) 화면에서 겹침 경고(Overlap warning) 외에 해당 파트의 채보(Transcription) 가능 노드 수를 요약하여 보여주는 기능을 추가했습니다. -- 신규 UI 요소에 대한 100% 테스트 커버리지를 보장하는 단위 테스트를 추가했습니다 (`apps/desktop/src/features/chords/index.test.tsx`, `apps/desktop/src/features/ranges/index.test.tsx`). +- 신규 UI 요소에 대한 100% 테스트 커버리지를 보장하는 단위 테스트를 추가했습니다 (`apps/desktop/src/features/chords/index.test.tsx`, `apps/desktop/src/features/ranges/index.test.tsx`). \ No newline at end of file From d9b8e97fa00f2e0e34e15654a3d5ffa6e34ab75b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 12:38:31 -0700 Subject: [PATCH 16/24] test(codeql): format revision contract --- .../tests/test_codeql_action_revision_contract.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/analysis-engine/tests/test_codeql_action_revision_contract.py b/services/analysis-engine/tests/test_codeql_action_revision_contract.py index cdca297df..03d26b369 100644 --- a/services/analysis-engine/tests/test_codeql_action_revision_contract.py +++ b/services/analysis-engine/tests/test_codeql_action_revision_contract.py @@ -40,7 +40,9 @@ def test_every_codeql_action_step_uses_the_same_reviewed_revision() -> None: references = _codeql_action_references() assert references - assert {revision_ref for _, _, revision_ref, _ in references} == {_EXPECTED_CODEQL_ACTION_SHA} + assert { + revision_ref for _, _, revision_ref, _ in references + } == {_EXPECTED_CODEQL_ACTION_SHA} expected_version = f"# {_EXPECTED_CODEQL_ACTION_VERSION}" assert all(expected_version in suffix for _, _, _, suffix in references) From 823a66200f7a9c846147d3632cc167c1c8f08c65 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 13:18:18 +0900 Subject: [PATCH 17/24] fix(codeql): converge lifecycle and PR evidence Advance every checked-in CodeQL Action phase to the same immutable v4.37.9 commit, preserve Scorecard pull-request evidence behind a trusted base-SHA normalization checkout, and route GitHub Actions Dependabot updates to the existing area: ci-cd taxonomy. The preceding commit holds the deterministic RED contracts; this commit supplies the smallest owning workflow/config fix. The upstream v4.37.9 tag resolves to cdf488f595d80d6e07e03d4674febd5ab45fa938 and is unsigned; no signed-tag claim is made. Split or predecessor checks and reviews do not transfer. --- .github/dependabot.yml | 2 +- .github/workflows/codeql.yml | 6 +- .github/workflows/ossf-scorecard.yml | 14 ++-- .github/workflows/trivy.yml | 2 +- .../codeql-action-atomic-revision.md | 76 +++++++++++++++++++ 5 files changed, 90 insertions(+), 10 deletions(-) create mode 100644 docs/doctoring/codeql-action-atomic-revision.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2df162d89..8221219aa 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -38,4 +38,4 @@ updates: open-pull-requests-limit: 10 labels: - "dependencies" - - "github-actions" + - "area: ci-cd" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 27c5b540f..755e60e8b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -32,8 +32,8 @@ jobs: - python steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + - uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 with: languages: ${{ matrix.language }} - - uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 - - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + - uses: github/codeql-action/autobuild@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 + - uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 2a4b6eaa9..81eebfabc 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -8,6 +8,10 @@ on: branches: - develop - main + pull_request: + branches: + - develop + - main permissions: read-all @@ -27,13 +31,13 @@ jobs: with: persist-credentials: false - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 - if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + if: github.event_name == 'pull_request' || github.ref == format('refs/heads/{0}', github.event.repository.default_branch) with: results_file: results.sarif results_format: sarif publish_results: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + if: github.event_name == 'pull_request' || github.ref == format('refs/heads/{0}', github.event.repository.default_branch) with: name: ossf-scorecard-results path: results.sarif @@ -41,7 +45,7 @@ jobs: scorecard-sarif-upload: name: scorecard-sarif-upload needs: analysis - if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + if: github.event_name == 'pull_request' || github.ref == format('refs/heads/{0}', github.event.repository.default_branch) runs-on: ubuntu-latest permissions: actions: read @@ -63,7 +67,7 @@ jobs: with: persist-credentials: false path: trusted-scorecard-scripts - ref: ${{ github.ref_name }} + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.ref_name }} - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: ossf-scorecard-results @@ -79,6 +83,6 @@ jobs: python3 trusted-scorecard-scripts/scripts/checks/normalize_scorecard_sarif.py scorecard-sarif/results.sarif normalized-scorecard-results.sarif - - uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 peeled commit; SHA pinning retained as supply-chain attack mitigation. + - uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9; immutable SHA pinning retained as supply-chain attack mitigation. with: sarif_file: normalized-scorecard-results.sarif diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index d79ec32e1..c8334cfd8 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -48,7 +48,7 @@ jobs: skip-dirs: 'services/analysis-engine/.venv' trivyignores: ./.trivyignore - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 peeled commit; SHA pinning retained as supply-chain attack mitigation. + uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9; immutable SHA pinning retained as supply-chain attack mitigation. if: always() with: sarif_file: trivy-results.sarif diff --git a/docs/doctoring/codeql-action-atomic-revision.md b/docs/doctoring/codeql-action-atomic-revision.md new file mode 100644 index 000000000..364044b8a --- /dev/null +++ b/docs/doctoring/codeql-action-atomic-revision.md @@ -0,0 +1,76 @@ +# Atomic CodeQL Action revision policy + +## Decision + +BandScope treats the CodeQL Action lifecycle as one supply-chain dependency. Every checked-in reference to `github/codeql-action/init`, `autobuild`, `analyze`, and `upload-sarif` must use the same reviewed full-length commit SHA and matching release annotation. + +The current reviewed revision is CodeQL Action `v4.37.9` at commit `cdf488f595d80d6e07e03d4674febd5ab45fa938`. Fresh upstream resolution on 2026-09-02 found annotated tag object `a35ac6e6798d72df5475948b28efb89edc2e19ca`, which points to that commit. The tag object is unsigned, so BandScope makes no signed-tag claim; the workflow execution identity is the pinned commit SHA. The v4.37.9 release was tagged on 2026-08-26 and advances the default CodeQL bundle to `2.26.4`. + +GitHub documents `init` as the phase that initializes CodeQL, `autobuild` as the optional automatic build phase, and `analyze` as the phase that finalizes the database, runs queries, and uploads results. `upload-sarif` publishes SARIF generated by other tools. These phases exchange state and therefore move together in this repository rather than through independent dependency pull requests. + +## Threat and compatibility boundary + +A full commit SHA is the immutable execution identity. Tags remain useful release labels but are not accepted as the workflow execution reference. Independently updating one phase can leave the repository with mixed JavaScript bundles, CodeQL CLI expectations, feature flags, or SARIF transport behavior. The atomic policy prevents both persistent drift and the transient mixed state caused by independently merged Dependabot component pull requests. + +```mermaid +flowchart LR + A[CodeQL init] --> B[CodeQL autobuild] + B --> C[CodeQL analyze] + C --> D[GitHub code scanning] + E[Trivy and Scorecard SARIF] --> F[CodeQL upload-sarif] + R[One reviewed release SHA] --> A + R --> B + R --> C + R --> F +``` + +The CodeQL revision update does not broaden application filesystem, network, model, database, IPC, or credential authority. + +## Pull-request Scorecard evidence + +OpenSSF Scorecard is also required to materialize evidence on BandScope pull requests. The workflow uses the ordinary `pull_request` event for `develop` and `main`; it does not use `pull_request_target`. For PR runs, the second checkout that supplies repository-owned SARIF extraction and normalization scripts is pinned to `github.event.pull_request.base.sha`, so an untrusted PR head cannot replace the trusted parser executed by the evidence job. Scorecard `publish_results` remains enabled only for the repository default branch. + +This is an evidence-collection boundary, not a claim that every fork or external actor receives write-capable code-scanning credentials. GitHub token permissions and repository policy still determine whether a particular run can upload SARIF. + +## Dependabot routing + +GitHub Actions updates use the repository's existing `area: ci-cd` taxonomy label together with `dependencies`. The former configured `github-actions` label did not exist and caused Dependabot to report a configuration error. A regression test prevents that invalid label from returning. + +## Verification contract + +`services/analysis-engine/tests/test_codeql_action_revision_contract.py` scans every checked-in workflow and fails unless: + +1. all CodeQL Action phases use the exact `v4.37.9` commit SHA; +2. every reference carries the matching `v4.37.9` annotation; and +3. `codeql.yml` keeps `init`, `autobuild`, and `analyze` on that same revision. + +The scanner deliberately recognizes mutable and malformed revision tokens such as `@v4` before enforcing the exact-SHA invariant. A tag-style reference therefore becomes a failing value rather than disappearing from the evidence set. + +`test_dependabot_label_contract.py` pins the GitHub Actions update label to `area: ci-cd`. `test_scorecard_pr_code_scanning_contract.py` pins the ordinary PR trigger, default-branch-only publication, and trusted base-SHA checkout used by the PR SARIF path. + +Repository CI, CodeQL, SAST, dependency/security scans, SBOM generation, central coverage evidence, automated review, independent approval, and branch protection must validate the final exact head. Results from split or predecessor pull requests are not transferable. + +## Update procedure + +1. Identify the newest supported CodeQL Action v4 release from the upstream repository. +2. Resolve its tag to the intended upstream commit, inspect tag verification and release notes, and record only claims supported by that evidence. +3. Update the executable contract first so the previous lifecycle becomes a deterministic RED state. +4. Update every `init`, `autobuild`, `analyze`, and `upload-sarif` reference in one canonical branch. +5. Preserve the Scorecard PR trust boundary and Dependabot routing contracts while reconciling competing lifecycle writers. +6. Run the focused contracts and complete repository gates on the resulting exact head. +7. Merge only after exact-current-head checks, current review findings, independent non-author approval, and branch protection all pass without bypass. +8. Close split dependency pull requests only after the coordinated lifecycle is accepted under the protected merge gate; never reuse their checks or approvals. + +## Rollback + +Rollback restores the previously accepted full-length SHA across every CodeQL Action phase in one reviewed commit. A partial rollback is prohibited. After rollback, rerun the same exact-head security, quality, SARIF publication, and review gates before accepting the branch. + +## References + +GitHub. (2026). *CodeQL Action v4.37.9* [Software release]. https://github.com/github/codeql-action/releases/tag/v4.37.9 + +GitHub. (2026). *CodeQL Bundle v2.26.4* [Software release]. https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4 + +GitHub. (n.d.). *CodeQL code scanning for compiled languages*. GitHub Docs. Retrieved September 2, 2026, from https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration/codeql-for-compiled-languages + +GitHub. (n.d.). *Secure use reference*. GitHub Docs. Retrieved September 2, 2026, from https://docs.github.com/en/actions/reference/security/secure-use From 784ca7221d06939aafd5fe37b865eb80bc2cebfb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:44:09 +0900 Subject: [PATCH 18/24] test(codeql): require pull-request head evidence --- .../test_codeql_pr_code_scanning_contract.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 services/analysis-engine/tests/test_codeql_pr_code_scanning_contract.py diff --git a/services/analysis-engine/tests/test_codeql_pr_code_scanning_contract.py b/services/analysis-engine/tests/test_codeql_pr_code_scanning_contract.py new file mode 100644 index 000000000..be2739e7e --- /dev/null +++ b/services/analysis-engine/tests/test_codeql_pr_code_scanning_contract.py @@ -0,0 +1,43 @@ +"""Contract tests for BandScope pull-request CodeQL evidence.""" + +from __future__ import annotations + +from pathlib import Path + + +REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +CODEQL_WORKFLOW_PATH = REPOSITORY_ROOT / ".github" / "workflows" / "codeql.yml" + + +def _codeql_workflow_text() -> str: + """Return the checked-in CodeQL workflow text.""" + return CODEQL_WORKFLOW_PATH.read_text(encoding="utf-8") + + +def _pull_request_event_section(workflow_text: str) -> str: + """Return the top-level pull-request trigger section before permissions.""" + event_marker = " pull_request:\n" + assert event_marker in workflow_text, "CodeQL must run on pull-request heads" + event_tail = workflow_text.split(event_marker, 1)[1] + return event_tail.split("\npermissions:", 1)[0] + + +def test_codeql_scans_supported_pull_request_targets() -> None: + """Require CodeQL evidence for ordinary pull requests to both protected branches.""" + workflow_text = _codeql_workflow_text() + pull_request_section = _pull_request_event_section(workflow_text) + + assert " branches:\n" in pull_request_section + assert " - develop\n" in pull_request_section + assert " - main\n" in pull_request_section + assert "pull_request_target:" not in workflow_text + + +def test_codeql_retains_protected_branch_push_reporting() -> None: + """Require existing protected-branch push reporting alongside pull-request evidence.""" + workflow_text = _codeql_workflow_text() + push_section = workflow_text.split(" push:\n", 1)[1].split(" pull_request:\n", 1)[0] + + assert " branches:\n" in push_section + assert " - develop\n" in push_section + assert " - main\n" in push_section From 7992447f25a766ee1b11b5f93778c8424d8d0e3c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:44:28 +0900 Subject: [PATCH 19/24] fix(codeql): scan pull-request heads --- .github/workflows/codeql.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 755e60e8b..2f98baea4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,10 +1,18 @@ name: codeql +# Temporary repository-local pull-request safety net: organization ruleset 18156473 +# does not yet attach ContextualWisdomLab/.github's codeql-pr.yml required workflow. +# Remove this pull_request trigger only after the live organization ruleset is verified +# to require the central CodeQL PR workflow for BandScope. on: push: branches: - develop - main + pull_request: + branches: + - develop + - main workflow_dispatch: permissions: From b48ab9598b04fa1a3cbb3f648e501463a1e7f2b3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:46:48 +0900 Subject: [PATCH 20/24] ci: add one-shot CodeQL policy repair --- .../source-fix-codeql-pr-contract.yml | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 .github/workflows/source-fix-codeql-pr-contract.yml diff --git a/.github/workflows/source-fix-codeql-pr-contract.yml b/.github/workflows/source-fix-codeql-pr-contract.yml new file mode 100644 index 000000000..370f00927 --- /dev/null +++ b/.github/workflows/source-fix-codeql-pr-contract.yml @@ -0,0 +1,183 @@ +name: CodeQL PR contract source repair + +on: + push: + branches: + - fix/codeql-action-consistency-v4-37-6 + +permissions: + contents: write + +env: + GIT_CONFIG_COUNT: "1" + GIT_CONFIG_KEY_0: init.defaultBranch + GIT_CONFIG_VALUE_0: develop + +concurrency: + group: source-fix-codeql-pr-contract-${{ github.ref }} + cancel-in-progress: false + +jobs: + repair_codeql_pr_contract: + name: repair CodeQL PR contract and self-delete + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + persist-credentials: true + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + version: "0.8.6" + enable-cache: false + - name: Verify exact branch authority + env: + STARTING_HEAD_SHA: ${{ github.sha }} + TARGET_BRANCH_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + remote_head_sha="$(git ls-remote origin "refs/heads/${TARGET_BRANCH_NAME}" | cut -f1)" + test "${remote_head_sha}" = "${STARTING_HEAD_SHA}" + - name: Repair stale event policy and traceability + run: | + set -euo pipefail + python3 - <<'PY' + from pathlib import Path + import re + + test_path = Path("services/analysis-engine/tests/test_supply_chain_policy.py") + test_source = test_path.read_text(encoding="utf-8") + stale_test_pattern = re.compile( + r"def test_central_governance_workflows_are_push_only_where_local_signals_remain\(\) -> None:\n" + r".*?" + r"(?=\n\ndef test_opencode_review_declares_top_level_token_permissions)", + re.DOTALL, + ) + stale_matches = stale_test_pattern.findall(test_source) + if len(stale_matches) != 1: + raise SystemExit( + f"expected one stale central-governance test, found {len(stale_matches)}" + ) + replacement_test = '''def test_local_security_workflows_match_pull_request_coverage_contract() -> None: + """Ensure each repository security signal follows its owned event contract.""" + repository_root = Path(__file__).resolve().parents[3] + workflows_directory = repository_root / ".github" / "workflows" + + assert not (workflows_directory / "dependency-review.yml").exists() + + pull_request_signal_names = ("codeql.yml", "ossf-scorecard.yml") + for workflow_filename in pull_request_signal_names: + workflow_path = workflows_directory / workflow_filename + assert workflow_path.exists(), ( + f"{workflow_filename} must retain repository-local security evidence" + ) + workflow_text = workflow_path.read_text(encoding="utf-8") + assert "push:" in workflow_text, ( + f"{workflow_filename} must retain protected-branch push reporting" + ) + assert "pull_request:" in workflow_text, ( + f"{workflow_filename} must collect pull-request-head evidence" + ) + assert "pull_request_target:" not in workflow_text + pull_request_section = workflow_text.split(" pull_request:\\n", 1)[1].split( + "\\npermissions:", 1 + )[0] + assert " - develop\\n" in pull_request_section + assert " - main\\n" in pull_request_section + + trivy_workflow_path = workflows_directory / "trivy.yml" + assert trivy_workflow_path.exists() + trivy_workflow_text = trivy_workflow_path.read_text(encoding="utf-8") + assert "push:" in trivy_workflow_text + assert "pull_request:" not in trivy_workflow_text + + supply_chain_module = load_module( + "scripts/checks/verify_supply_chain.py", "verify_supply_chain_central" + ) + required_workflow_paths = { + required_path.as_posix() for required_path in supply_chain_module.REQUIRED_FILES + } + assert ".github/workflows/dependency-review.yml" not in required_workflow_paths + assert ".github/workflows/codeql.yml" in required_workflow_paths + assert ".github/workflows/ossf-scorecard.yml" in required_workflow_paths + ''' + test_path.write_text( + stale_test_pattern.sub(replacement_test.rstrip(), test_source), encoding="utf-8" + ) + + policy_path = Path("scripts/checks/verify_supply_chain.py") + policy_source = policy_path.read_text(encoding="utf-8") + stale_policy_comment = ''' # Dependency review runs via the org-level required workflow in + # ContextualWisdomLab/.github; repo-local CodeQL and Scorecard stay push-only + # so GitHub/Scorecard can still observe SAST and supply-chain security tabs. + ''' + repaired_policy_comment = ''' # Dependency review runs via the org-level required workflow in + # ContextualWisdomLab/.github. Repository-local CodeQL and Scorecard retain push + # reporting and also collect PR-head evidence while the live organization ruleset + # does not attach the corresponding central required workflows. + ''' + if policy_source.count(stale_policy_comment) != 1: + raise SystemExit("stale supply-chain policy comment changed concurrently") + policy_path.write_text( + policy_source.replace(stale_policy_comment, repaired_policy_comment), encoding="utf-8" + ) + + doctoring_path = Path("docs/doctoring/codeql-action-atomic-revision.md") + doctoring_source = doctoring_path.read_text(encoding="utf-8") + doctoring_marker = "## Pull-request CodeQL evidence" + if doctoring_marker not in doctoring_source: + doctoring_source += ''' + + ## Pull-request CodeQL evidence + + BandScope requires CodeQL analysis to materialize on ordinary `pull_request` heads targeting `develop` and `main` as well as on protected-branch pushes. On 2026-09-02, live organization ruleset `18156473` did not attach `ContextualWisdomLab/.github`'s central `codeql-pr.yml` required workflow, so the repository-local `codeql.yml` carries the temporary PR-head safety net. `pull_request_target` is explicitly forbidden for this lane. + + The local trigger is a compatibility boundary, not a second source of organization policy. Remove it only after the live organization ruleset is independently verified to require the central CodeQL PR workflow for BandScope. The unique CodeQL PR-head behavior from #1144 is carried here by the canonical atomic CodeQL lifecycle owner, together with the newer v4.37.9 lifecycle pin and executable regression coverage; predecessor checks or approvals do not transfer. + ''' + doctoring_path.write_text(doctoring_source, encoding="utf-8") + + changelog_path = Path("CHANGELOG.md") + changelog_source = changelog_path.read_text(encoding="utf-8") + changelog_entry = ( + "- Restored CodeQL analysis for pull-request heads targeting `develop` and `main` " + "while the live organization ruleset lacks the central CodeQL required workflow; " + "the repository-local trigger is removed only after central enforcement is verified.\n" + ) + if changelog_entry not in changelog_source: + fixed_marker = "### Fixed\n\n" + if changelog_source.count(fixed_marker) < 1: + raise SystemExit("CHANGELOG Unreleased Fixed section not found") + changelog_source = changelog_source.replace( + fixed_marker, fixed_marker + changelog_entry, 1 + ) + changelog_path.write_text(changelog_source, encoding="utf-8") + PY + - name: Verify focused CodeQL contracts + run: | + set -euo pipefail + uv sync --project services/analysis-engine --group dev --locked + uv run --project services/analysis-engine pytest -q \ + services/analysis-engine/tests/test_codeql_pr_code_scanning_contract.py \ + services/analysis-engine/tests/test_supply_chain_policy.py \ + services/analysis-engine/tests/test_codeql_action_revision_contract.py \ + services/analysis-engine/tests/test_scorecard_pr_code_scanning_contract.py + git diff --check + - name: Commit repair and remove source-fix workflow + env: + STARTING_HEAD_SHA: ${{ github.sha }} + TARGET_BRANCH_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + rm .github/workflows/source-fix-codeql-pr-contract.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --check + git commit -m "fix(codeql): reconcile PR-head security contract" + remote_head_sha="$(git ls-remote origin "refs/heads/${TARGET_BRANCH_NAME}" | cut -f1)" + test "${remote_head_sha}" = "${STARTING_HEAD_SHA}" + git push origin "HEAD:refs/heads/${TARGET_BRANCH_NAME}" From 212467118bed47793fcf12a3819d3a0e122d6678 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:52:53 +0900 Subject: [PATCH 21/24] ci: retire superseded CodeQL source-fix helper --- .../source-fix-codeql-pr-contract.yml | 183 ------------------ 1 file changed, 183 deletions(-) delete mode 100644 .github/workflows/source-fix-codeql-pr-contract.yml diff --git a/.github/workflows/source-fix-codeql-pr-contract.yml b/.github/workflows/source-fix-codeql-pr-contract.yml deleted file mode 100644 index 370f00927..000000000 --- a/.github/workflows/source-fix-codeql-pr-contract.yml +++ /dev/null @@ -1,183 +0,0 @@ -name: CodeQL PR contract source repair - -on: - push: - branches: - - fix/codeql-action-consistency-v4-37-6 - -permissions: - contents: write - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: init.defaultBranch - GIT_CONFIG_VALUE_0: develop - -concurrency: - group: source-fix-codeql-pr-contract-${{ github.ref }} - cancel-in-progress: false - -jobs: - repair_codeql_pr_contract: - name: repair CodeQL PR contract and self-delete - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - fetch-depth: 0 - persist-credentials: true - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: "3.12" - - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 - with: - version: "0.8.6" - enable-cache: false - - name: Verify exact branch authority - env: - STARTING_HEAD_SHA: ${{ github.sha }} - TARGET_BRANCH_NAME: ${{ github.ref_name }} - run: | - set -euo pipefail - remote_head_sha="$(git ls-remote origin "refs/heads/${TARGET_BRANCH_NAME}" | cut -f1)" - test "${remote_head_sha}" = "${STARTING_HEAD_SHA}" - - name: Repair stale event policy and traceability - run: | - set -euo pipefail - python3 - <<'PY' - from pathlib import Path - import re - - test_path = Path("services/analysis-engine/tests/test_supply_chain_policy.py") - test_source = test_path.read_text(encoding="utf-8") - stale_test_pattern = re.compile( - r"def test_central_governance_workflows_are_push_only_where_local_signals_remain\(\) -> None:\n" - r".*?" - r"(?=\n\ndef test_opencode_review_declares_top_level_token_permissions)", - re.DOTALL, - ) - stale_matches = stale_test_pattern.findall(test_source) - if len(stale_matches) != 1: - raise SystemExit( - f"expected one stale central-governance test, found {len(stale_matches)}" - ) - replacement_test = '''def test_local_security_workflows_match_pull_request_coverage_contract() -> None: - """Ensure each repository security signal follows its owned event contract.""" - repository_root = Path(__file__).resolve().parents[3] - workflows_directory = repository_root / ".github" / "workflows" - - assert not (workflows_directory / "dependency-review.yml").exists() - - pull_request_signal_names = ("codeql.yml", "ossf-scorecard.yml") - for workflow_filename in pull_request_signal_names: - workflow_path = workflows_directory / workflow_filename - assert workflow_path.exists(), ( - f"{workflow_filename} must retain repository-local security evidence" - ) - workflow_text = workflow_path.read_text(encoding="utf-8") - assert "push:" in workflow_text, ( - f"{workflow_filename} must retain protected-branch push reporting" - ) - assert "pull_request:" in workflow_text, ( - f"{workflow_filename} must collect pull-request-head evidence" - ) - assert "pull_request_target:" not in workflow_text - pull_request_section = workflow_text.split(" pull_request:\\n", 1)[1].split( - "\\npermissions:", 1 - )[0] - assert " - develop\\n" in pull_request_section - assert " - main\\n" in pull_request_section - - trivy_workflow_path = workflows_directory / "trivy.yml" - assert trivy_workflow_path.exists() - trivy_workflow_text = trivy_workflow_path.read_text(encoding="utf-8") - assert "push:" in trivy_workflow_text - assert "pull_request:" not in trivy_workflow_text - - supply_chain_module = load_module( - "scripts/checks/verify_supply_chain.py", "verify_supply_chain_central" - ) - required_workflow_paths = { - required_path.as_posix() for required_path in supply_chain_module.REQUIRED_FILES - } - assert ".github/workflows/dependency-review.yml" not in required_workflow_paths - assert ".github/workflows/codeql.yml" in required_workflow_paths - assert ".github/workflows/ossf-scorecard.yml" in required_workflow_paths - ''' - test_path.write_text( - stale_test_pattern.sub(replacement_test.rstrip(), test_source), encoding="utf-8" - ) - - policy_path = Path("scripts/checks/verify_supply_chain.py") - policy_source = policy_path.read_text(encoding="utf-8") - stale_policy_comment = ''' # Dependency review runs via the org-level required workflow in - # ContextualWisdomLab/.github; repo-local CodeQL and Scorecard stay push-only - # so GitHub/Scorecard can still observe SAST and supply-chain security tabs. - ''' - repaired_policy_comment = ''' # Dependency review runs via the org-level required workflow in - # ContextualWisdomLab/.github. Repository-local CodeQL and Scorecard retain push - # reporting and also collect PR-head evidence while the live organization ruleset - # does not attach the corresponding central required workflows. - ''' - if policy_source.count(stale_policy_comment) != 1: - raise SystemExit("stale supply-chain policy comment changed concurrently") - policy_path.write_text( - policy_source.replace(stale_policy_comment, repaired_policy_comment), encoding="utf-8" - ) - - doctoring_path = Path("docs/doctoring/codeql-action-atomic-revision.md") - doctoring_source = doctoring_path.read_text(encoding="utf-8") - doctoring_marker = "## Pull-request CodeQL evidence" - if doctoring_marker not in doctoring_source: - doctoring_source += ''' - - ## Pull-request CodeQL evidence - - BandScope requires CodeQL analysis to materialize on ordinary `pull_request` heads targeting `develop` and `main` as well as on protected-branch pushes. On 2026-09-02, live organization ruleset `18156473` did not attach `ContextualWisdomLab/.github`'s central `codeql-pr.yml` required workflow, so the repository-local `codeql.yml` carries the temporary PR-head safety net. `pull_request_target` is explicitly forbidden for this lane. - - The local trigger is a compatibility boundary, not a second source of organization policy. Remove it only after the live organization ruleset is independently verified to require the central CodeQL PR workflow for BandScope. The unique CodeQL PR-head behavior from #1144 is carried here by the canonical atomic CodeQL lifecycle owner, together with the newer v4.37.9 lifecycle pin and executable regression coverage; predecessor checks or approvals do not transfer. - ''' - doctoring_path.write_text(doctoring_source, encoding="utf-8") - - changelog_path = Path("CHANGELOG.md") - changelog_source = changelog_path.read_text(encoding="utf-8") - changelog_entry = ( - "- Restored CodeQL analysis for pull-request heads targeting `develop` and `main` " - "while the live organization ruleset lacks the central CodeQL required workflow; " - "the repository-local trigger is removed only after central enforcement is verified.\n" - ) - if changelog_entry not in changelog_source: - fixed_marker = "### Fixed\n\n" - if changelog_source.count(fixed_marker) < 1: - raise SystemExit("CHANGELOG Unreleased Fixed section not found") - changelog_source = changelog_source.replace( - fixed_marker, fixed_marker + changelog_entry, 1 - ) - changelog_path.write_text(changelog_source, encoding="utf-8") - PY - - name: Verify focused CodeQL contracts - run: | - set -euo pipefail - uv sync --project services/analysis-engine --group dev --locked - uv run --project services/analysis-engine pytest -q \ - services/analysis-engine/tests/test_codeql_pr_code_scanning_contract.py \ - services/analysis-engine/tests/test_supply_chain_policy.py \ - services/analysis-engine/tests/test_codeql_action_revision_contract.py \ - services/analysis-engine/tests/test_scorecard_pr_code_scanning_contract.py - git diff --check - - name: Commit repair and remove source-fix workflow - env: - STARTING_HEAD_SHA: ${{ github.sha }} - TARGET_BRANCH_NAME: ${{ github.ref_name }} - run: | - set -euo pipefail - rm .github/workflows/source-fix-codeql-pr-contract.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git diff --cached --check - git commit -m "fix(codeql): reconcile PR-head security contract" - remote_head_sha="$(git ls-remote origin "refs/heads/${TARGET_BRANCH_NAME}" | cut -f1)" - test "${remote_head_sha}" = "${STARTING_HEAD_SHA}" - git push origin "HEAD:refs/heads/${TARGET_BRANCH_NAME}" From d81d336e7de6fad35756b3bf522a0e7b313a90d2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:56:24 +0900 Subject: [PATCH 22/24] test(security): transfer PR scanning ownership centrally --- .../test_codeql_pr_code_scanning_contract.py | 28 +++++---------- ...est_scorecard_pr_code_scanning_contract.py | 34 ++++++++++--------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/services/analysis-engine/tests/test_codeql_pr_code_scanning_contract.py b/services/analysis-engine/tests/test_codeql_pr_code_scanning_contract.py index be2739e7e..e77911e9b 100644 --- a/services/analysis-engine/tests/test_codeql_pr_code_scanning_contract.py +++ b/services/analysis-engine/tests/test_codeql_pr_code_scanning_contract.py @@ -1,4 +1,4 @@ -"""Contract tests for BandScope pull-request CodeQL evidence.""" +"""Contract tests for BandScope's local/central CodeQL ownership boundary.""" from __future__ import annotations @@ -10,33 +10,23 @@ def _codeql_workflow_text() -> str: - """Return the checked-in CodeQL workflow text.""" + """Return the checked-in repository-local CodeQL workflow text.""" return CODEQL_WORKFLOW_PATH.read_text(encoding="utf-8") -def _pull_request_event_section(workflow_text: str) -> str: - """Return the top-level pull-request trigger section before permissions.""" - event_marker = " pull_request:\n" - assert event_marker in workflow_text, "CodeQL must run on pull-request heads" - event_tail = workflow_text.split(event_marker, 1)[1] - return event_tail.split("\npermissions:", 1)[0] - - -def test_codeql_scans_supported_pull_request_targets() -> None: - """Require CodeQL evidence for ordinary pull requests to both protected branches.""" +def test_local_codeql_defers_pull_request_evidence_to_central_required_workflow() -> None: + """Keep PR scanning single-owned by the central required CodeQL workflow.""" workflow_text = _codeql_workflow_text() - pull_request_section = _pull_request_event_section(workflow_text) - assert " branches:\n" in pull_request_section - assert " - develop\n" in pull_request_section - assert " - main\n" in pull_request_section + assert "central required CodeQL PR workflow" in workflow_text + assert " pull_request:\n" not in workflow_text assert "pull_request_target:" not in workflow_text -def test_codeql_retains_protected_branch_push_reporting() -> None: - """Require existing protected-branch push reporting alongside pull-request evidence.""" +def test_local_codeql_retains_protected_branch_push_reporting() -> None: + """Keep repository-local CodeQL reporting on protected-branch pushes.""" workflow_text = _codeql_workflow_text() - push_section = workflow_text.split(" push:\n", 1)[1].split(" pull_request:\n", 1)[0] + push_section = workflow_text.split(" push:\n", 1)[1].split(" workflow_dispatch:\n", 1)[0] assert " branches:\n" in push_section assert " - develop\n" in push_section diff --git a/services/analysis-engine/tests/test_scorecard_pr_code_scanning_contract.py b/services/analysis-engine/tests/test_scorecard_pr_code_scanning_contract.py index 3d03fd0df..d004d769d 100644 --- a/services/analysis-engine/tests/test_scorecard_pr_code_scanning_contract.py +++ b/services/analysis-engine/tests/test_scorecard_pr_code_scanning_contract.py @@ -1,30 +1,32 @@ -"""Regression contract for OpenSSF Scorecard evidence on pull-request heads.""" +"""Regression contract for repository-local OpenSSF Scorecard ownership.""" from __future__ import annotations from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parents[3] -WORKFLOW = REPO_ROOT / ".github" / "workflows" / "ossf-scorecard.yml" +REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +SCORECARD_WORKFLOW_PATH = REPOSITORY_ROOT / ".github" / "workflows" / "ossf-scorecard.yml" DEFAULT_BRANCH_GUARD = "github.ref == format('refs/heads/{0}', github.event.repository.default_branch)" -PR_OR_DEFAULT_BRANCH = f"github.event_name == 'pull_request' || {DEFAULT_BRANCH_GUARD}" -PR_SAFE_TRUSTED_REF = ( - "ref: ${{ github.event_name == 'pull_request' && " - "github.event.pull_request.base.sha || github.ref_name }}" -) -def test_scorecard_produces_pr_code_scanning_evidence_without_pr_publishing() -> None: - """Keep PR SARIF coverage while publishing only trusted default-branch runs.""" - contents = WORKFLOW.read_text(encoding="utf-8") +def test_local_scorecard_defers_pull_request_evidence_to_central_required_workflow() -> None: + """Keep PR Scorecard evidence single-owned by the central required workflow.""" + workflow_text = SCORECARD_WORKFLOW_PATH.read_text(encoding="utf-8") - assert "pull_request_target:" not in contents - assert " pull_request:\n branches:\n - develop\n - main\n" in contents - assert contents.count(f"if: {PR_OR_DEFAULT_BRANCH}") == 3 + assert "central required Scorecard PR workflow" in workflow_text + assert " pull_request:\n" not in workflow_text + assert "pull_request_target:" not in workflow_text + + +def test_local_scorecard_publishes_only_from_the_default_branch() -> None: + """Keep repository publication and SARIF upload scoped to trusted default-branch runs.""" + workflow_text = SCORECARD_WORKFLOW_PATH.read_text(encoding="utf-8") + + assert workflow_text.count(f"if: {DEFAULT_BRANCH_GUARD}") == 3 publish_lines = [ line.strip() - for line in contents.splitlines() + for line in workflow_text.splitlines() if line.strip().startswith("publish_results:") ] assert publish_lines == [f"publish_results: ${{{{ {DEFAULT_BRANCH_GUARD} }}}}"] - assert PR_SAFE_TRUSTED_REF in contents + assert "ref: ${{ github.ref_name }}" in workflow_text From a5b8dbcf7f3fc5b1dd40c73b9e4a64801180d1d9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:56:51 +0900 Subject: [PATCH 23/24] fix(security): defer PR CodeQL to central owner --- .github/workflows/codeql.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2f98baea4..d159da0c9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,18 +1,13 @@ name: codeql -# Temporary repository-local pull-request safety net: organization ruleset 18156473 -# does not yet attach ContextualWisdomLab/.github's codeql-pr.yml required workflow. -# Remove this pull_request trigger only after the live organization ruleset is verified -# to require the central CodeQL PR workflow for BandScope. +# Organization ruleset 18156473 now requires ContextualWisdomLab/.github's +# central required CodeQL PR workflow. Keep this repository-owned workflow +# push-only so PR evidence has one security owner and duplicate scans do not race. on: push: branches: - develop - main - pull_request: - branches: - - develop - - main workflow_dispatch: permissions: From e9f222e1ca6e9f818ed1da4c47a522c8260bd5a9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:57:23 +0900 Subject: [PATCH 24/24] fix(security): defer PR Scorecard to central owner --- .github/workflows/ossf-scorecard.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 81eebfabc..2f1117f9a 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -1,5 +1,8 @@ name: ossf-scorecard +# Organization ruleset 18156473 now requires ContextualWisdomLab/.github's +# central required Scorecard PR workflow. Keep this repository-owned workflow +# on trusted branch/scheduled execution so PR evidence has one control-plane owner. on: workflow_dispatch: schedule: @@ -8,10 +11,6 @@ on: branches: - develop - main - pull_request: - branches: - - develop - - main permissions: read-all @@ -31,13 +30,13 @@ jobs: with: persist-credentials: false - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 - if: github.event_name == 'pull_request' || github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) with: results_file: results.sarif results_format: sarif publish_results: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - if: github.event_name == 'pull_request' || github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) with: name: ossf-scorecard-results path: results.sarif @@ -45,7 +44,7 @@ jobs: scorecard-sarif-upload: name: scorecard-sarif-upload needs: analysis - if: github.event_name == 'pull_request' || github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) runs-on: ubuntu-latest permissions: actions: read @@ -67,7 +66,7 @@ jobs: with: persist-credentials: false path: trusted-scorecard-scripts - ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.ref_name }} + ref: ${{ github.ref_name }} - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: ossf-scorecard-results