Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
efd875b
fix(security): keep every CodeQL Action phase on one revision
seonghobae Aug 14, 2026
ec2f030
style(test): format CodeQL revision contract
seonghobae Aug 14, 2026
c453203
ci: add one-shot Ruff formatter for PR 780
seonghobae Aug 14, 2026
1348c6d
style(test): apply canonical Ruff formatting
github-actions[bot] Aug 14, 2026
0a6ae2b
ci: retrigger exact-head validation after canonical Ruff formatting
seonghobae Aug 14, 2026
f4f40a1
fix(security): advance atomic CodeQL revision to v4.37.7
seonghobae Aug 14, 2026
0e57921
test(codeql): detect mutable action references
seonghobae Aug 15, 2026
30d92d5
fix(codeql): scan mutable refs before enforcing SHA pin
seonghobae Aug 15, 2026
738495c
docs(codeql): document mutable-ref detection
seonghobae Aug 15, 2026
c43bf95
test(codeql): require atomic v4.37.8 revision
seonghobae Aug 25, 2026
6955468
build(codeql): advance analysis phases to v4.37.8
seonghobae Aug 25, 2026
81bcc60
build(codeql): advance Scorecard upload to v4.37.8
seonghobae Aug 25, 2026
708cb3d
build(codeql): advance Trivy SARIF upload to v4.37.8
seonghobae Aug 25, 2026
b07c0ca
docs(codeql): record v4.37.8 atomic revision
seonghobae Aug 25, 2026
b860c49
docs(changelog): record CodeQL v4.37.8
seonghobae Aug 25, 2026
d9b8e97
test(codeql): format revision contract
seonghobae Aug 25, 2026
e1ed1ce
test(codeql): require one current lifecycle
seonghobae Sep 2, 2026
823a662
fix(codeql): converge lifecycle and PR evidence
seonghobae Sep 2, 2026
784ca72
test(codeql): require pull-request head evidence
seonghobae Sep 2, 2026
7992447
fix(codeql): scan pull-request heads
seonghobae Sep 2, 2026
b48ab95
ci: add one-shot CodeQL policy repair
seonghobae Sep 2, 2026
2124671
ci: retire superseded CodeQL source-fix helper
seonghobae Sep 2, 2026
d81d336
test(security): transfer PR scanning ownership centrally
seonghobae Sep 2, 2026
a5b8dbc
fix(security): defer PR CodeQL to central owner
seonghobae Sep 2, 2026
e9f222e
fix(security): defer PR Scorecard to central owner
seonghobae Sep 2, 2026
a08e154
Merge protected develop into CodeQL lifecycle owner
seonghobae Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ updates:
open-pull-requests-limit: 10
labels:
- "dependencies"
- "github-actions"
- "area: ci-cd"
9 changes: 6 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: codeql

# 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:
Expand Down Expand Up @@ -32,8 +35,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
5 changes: 4 additions & 1 deletion .github/workflows/ossf-scorecard.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -79,6 +82,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
2 changes: 1 addition & 1 deletion .github/workflows/trivy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
76 changes: 76 additions & 0 deletions docs/doctoring/codeql-action-atomic-revision.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""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 = "cdf488f595d80d6e07e03d4674febd5ab45fa938"
_EXPECTED_CODEQL_ACTION_VERSION = "v4.37.9"
_CODEQL_ACTION_REFERENCE = re.compile(
r"github/codeql-action/(init|autobuild|analyze|upload-sarif)@([^\s#]+)([^\n]*)"
)


def _codeql_action_references() -> list[tuple[Path, str, str, str]]:
"""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_ref, suffix in matches:
reference = (workflow_path, action_name, revision_ref, suffix.strip())
references.append(reference)
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()

assert references
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)


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_ref
for action_name, revision_ref, _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,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Contract tests for BandScope's local/central CodeQL ownership boundary."""

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 repository-local CodeQL workflow text."""
return CODEQL_WORKFLOW_PATH.read_text(encoding="utf-8")


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()

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_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(" workflow_dispatch:\n", 1)[0]

assert " branches:\n" in push_section
assert " - develop\n" in push_section
assert " - main\n" in push_section
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Regression contract for Dependabot's configured repository labels."""

from __future__ import annotations

from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[3]
DEPENDABOT_CONFIG = REPO_ROOT / ".github" / "dependabot.yml"


def test_github_actions_updates_use_repository_ci_cd_taxonomy() -> None:
"""Keep GitHub Actions updates on an existing CI/CD taxonomy label."""
config = DEPENDABOT_CONFIG.read_text(encoding="utf-8")
github_actions = config.split('package-ecosystem: "github-actions"', maxsplit=1)[1]

assert '- "area: ci-cd"' in github_actions
assert '- "github-actions"' not in github_actions
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Regression contract for repository-local OpenSSF Scorecard ownership."""

from __future__ import annotations

from pathlib import Path

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)"


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 "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 workflow_text.splitlines()
if line.strip().startswith("publish_results:")
]
assert publish_lines == [f"publish_results: ${{{{ {DEFAULT_BRANCH_GUARD} }}}}"]
assert "ref: ${{ github.ref_name }}" in workflow_text
Loading