diff --git a/.github/workflows/codeql-pr.yml b/.github/workflows/codeql-pr.yml new file mode 100644 index 00000000..85145296 --- /dev/null +++ b/.github/workflows/codeql-pr.yml @@ -0,0 +1,124 @@ +# Uploads CodeQL code scanning analyses on every PR so the org ruleset +# "CWL Central required workflows" -> code_scanning(CodeQL) can evaluate +# mergeability. Without PR-head and merge-preview SARIF on merge_commit_sha, +# approved PRs stay mergeStateStatus=BLOCKED with +# "Code scanning is waiting for results from CodeQL". +name: CodeQL PR + +on: + pull_request: + branches: [main, master, develop] + +permissions: + contents: read + +jobs: + detect-languages: + name: Detect CodeQL languages + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.detect.outputs.matrix }} + steps: + - name: Checkout PR head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} + + - name: Build language matrix + id: detect + run: | + matrix='[]' + if [ -d .github/workflows ]; then + matrix=$(echo "$matrix" | jq -c '. + [{"language":"actions","build-mode":"none"}]') + fi + if find . -type f \( -name '*.js' -o -name '*.jsx' -o -name '*.ts' -o -name '*.tsx' \) \ + -not -path './.git/*' | head -1 | grep -q .; then + matrix=$(echo "$matrix" | jq -c '. + [{"language":"javascript-typescript","build-mode":"none"}]') + fi + if find . -type f -name '*.py' -not -path './.git/*' | head -1 | grep -q .; then + matrix=$(echo "$matrix" | jq -c '. + [{"language":"python","build-mode":"none"}]') + fi + if [ "$(echo "$matrix" | jq 'length')" -eq 0 ]; then + matrix='[{"language":"actions","build-mode":"none"}]' + fi + { + echo 'matrix<> "$GITHUB_OUTPUT" + + analyze-head: + name: CodeQL compatibility analysis (${{ matrix.language }}) + needs: detect-languages + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.detect-languages.outputs.matrix) }} + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} + + - name: Initialize CodeQL + uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + with: + category: "/language:${{ matrix.language }}" + upload: always + ref: ${{ format('refs/pull/{0}/head', github.event.pull_request.number) }} + sha: ${{ github.event.pull_request.head.sha }} + + analyze-merge: + name: CodeQL merge preview (${{ matrix.language }}) + needs: detect-languages + if: github.event.pull_request.merge_commit_sha != '' + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.detect-languages.outputs.matrix) }} + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout merge preview + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + ref: ${{ format('refs/pull/{0}/merge', github.event.pull_request.number) }} + + - name: Initialize CodeQL + uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + with: + category: "/language:${{ matrix.language }}-merge" + upload: always + ref: ${{ format('refs/pull/{0}/merge', github.event.pull_request.number) }} + sha: ${{ github.event.pull_request.merge_commit_sha }} \ No newline at end of file diff --git a/.github/workflows/osv-scanner-pr.yml b/.github/workflows/osv-scanner-pr.yml index 00d2b3d5..b3c8a58b 100644 --- a/.github/workflows/osv-scanner-pr.yml +++ b/.github/workflows/osv-scanner-pr.yml @@ -6,7 +6,7 @@ name: OSV-Scanner PR on: pull_request: - branches: [main] + branches: [main, master, develop] permissions: # Upload SARIF to Security > Code Scanning. See github/codeql-action#2117. diff --git a/.github/workflows/scorecard-pr.yml b/.github/workflows/scorecard-pr.yml index 15771dc4..f5436bfa 100644 --- a/.github/workflows/scorecard-pr.yml +++ b/.github/workflows/scorecard-pr.yml @@ -13,7 +13,7 @@ name: Scorecard PR on: pull_request: - branches: [main] + branches: [main, master, develop] permissions: contents: read diff --git a/docs/org-required-workflow-rollout.md b/docs/org-required-workflow-rollout.md index 1f1a48df..4d910e26 100644 --- a/docs/org-required-workflow-rollout.md +++ b/docs/org-required-workflow-rollout.md @@ -16,6 +16,9 @@ Use an organization repository ruleset instead of copying workflow files into ea - `.github/workflows/strix.yml` - `.github/workflows/opencode-review.yml` - `.github/workflows/pr-review-merge-scheduler.yml` + - `.github/workflows/osv-scanner-pr.yml` + - `.github/workflows/scorecard-pr.yml` + - `.github/workflows/codeql-pr.yml` - Required workflow ref: `refs/heads/main` - Last verified workflow implementation base commit: `ef9950e6b55bf943c0295e1df3e34c94210d21cc` (`#283`) - Required workflow trigger support: `pull_request_target`, `push`, `workflow_run` @@ -44,6 +47,44 @@ The central `.github/workflows/opencode-review.yml` is now part of the active or Keep the OpenCode required workflow active only while the central workflow keeps proving current-head coverage, CodeGraph initialization, bounded evidence, model review output, and approval-gate publication on the current head. +## Code scanning required workflow posture + +The central `.github/workflows/codeql-pr.yml`, `.github/workflows/scorecard-pr.yml`, +and `.github/workflows/osv-scanner-pr.yml` workflows supply PR-head and merge-preview +code scanning analyses for ruleset `18156473` `code_scanning` (CodeQL, Scorecard, +osv-scanner). They trigger on pull requests to `main`, `master`, and `develop` so +Git Flow repositories on `develop` inherit the same merge gate as GitHub Flow repos. + +CodeQL merge preview checks out `refs/pull//merge` and uploads SARIF with +`sha: pull_request.merge_commit_sha` because the ruleset evaluates that commit, +not the ephemeral merge ref OID. + +Repository-local `codeql.yml` push/default-branch scans may remain for branch +history, but PR merge gates should rely on the central `codeql-pr.yml` workflow. + +### Repository-local CodeQL inventory (2026-07-04) + +Org audit of default-branch workflow files. Repos without any local CodeQL +workflow depend entirely on central `codeql-pr.yml` once ruleset `18156473` +includes that path; they are the most exposed to +`Code scanning is waiting for results from CodeQL` until the ruleset update +lands. + +| Repository | Default branch | Local CodeQL workflow | PR trigger | merge_commit_sha SARIF | +| --- | --- | --- | ---: | ---: | +| `aFIPC` | `master` | `codeql.yml` | yes | no | +| `bandscope` | `develop` | `codeql.yml` | yes | no | +| `newsdom-api` | `develop` | `codeql.yml` | yes | no | +| `pg-erd-cloud` | `main` | `codeql.yml`, `codeql-backfill.yml` | yes (`codeql.yml`) | no | +| `xtrmLLMBatchPython` | `develop` | `codeql.yml` | yes | no | +| `naruon` | `develop` | `codeql.yml` | yes (temporary; PR `#916` retires PR trigger) | yes (repo-local interim fix) | +| all other public non-fork org repos | varies | none observed | — | — | + +No repository-local PR CodeQL workflow besides `naruon` uploads merge-preview +SARIF on `merge_commit_sha`. Centralizing through `codeql-pr.yml` fixes every +inherited repository in one ruleset change; per-repo deletion of PR triggers is +optional cleanup to avoid duplicate scans. + ## Scheduler required workflow posture The central `.github/workflows/pr-review-merge-scheduler.yml` is now part of the active organization required workflow ruleset. diff --git a/tests/test_codeql_pr_workflow_contract.py b/tests/test_codeql_pr_workflow_contract.py new file mode 100644 index 00000000..5833691d --- /dev/null +++ b/tests/test_codeql_pr_workflow_contract.py @@ -0,0 +1,24 @@ +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] + + +def test_codeql_pr_workflow_uploads_head_and_merge_sarif_for_ruleset_gate() -> None: + workflow = (REPO_ROOT / ".github/workflows/codeql-pr.yml").read_text( + encoding="utf-8" + ) + + assert "name: CodeQL PR" in workflow + assert "branches: [main, master, develop]" in workflow + assert "upload: always" in workflow + assert "detect-languages:" in workflow + assert "analyze-head:" in workflow + assert "analyze-merge:" in workflow + assert "merge_commit_sha != ''" in workflow + assert "CodeQL merge preview" in workflow + assert "github.event.pull_request.head.sha" in workflow + assert "github.event.pull_request.merge_commit_sha" in workflow + assert "refs/pull/{0}/head" in workflow + assert "refs/pull/{0}/merge" in workflow + assert "security-events: write" in workflow \ No newline at end of file