From 1a25b125d6834979094466789fac6e0462143eb0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 15:52:58 +0900 Subject: [PATCH 01/11] test(security): require literal PR-head scanner checkout --- tests/test_security_scan_exact_head.py | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/test_security_scan_exact_head.py diff --git a/tests/test_security_scan_exact_head.py b/tests/test_security_scan_exact_head.py new file mode 100644 index 000000000..3b6e34a57 --- /dev/null +++ b/tests/test_security_scan_exact_head.py @@ -0,0 +1,57 @@ +"""Exact-head contracts for the organization security scanner workflow.""" + +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +WORKFLOW_PATH = REPO_ROOT / ".github" / "workflows" / "security-scan.yml" + + +def workflow_job(workflow: str, job_name: str) -> str: + """Return one top-level job block from the central security workflow. + + The workflow uses two-space-indented job identifiers. Normalizing line + endings keeps this contract deterministic on Windows and Unix checkouts. + """ + + normalized = workflow.replace("\r\n", "\n").replace("\r", "\n") + marker = f"\n {job_name}:\n" + start = normalized.index(marker) + len(marker) + remaining = normalized[start:] + candidates = [ + offset + for line in remaining.splitlines(keepends=True) + if (offset := remaining.find(line)) >= 0 + and line.startswith(" ") + and not line.startswith(" ") + and line.rstrip().endswith(":") + ] + if not candidates: + return remaining + first = min(offset for offset in candidates if offset > 0) + return remaining[:first] + + +def test_repository_scanners_checkout_the_literal_pull_request_head() -> None: + """Trivy and Scorecard must never scan GitHub's synthetic merge ref.""" + + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + exact_repository = "repository: ${{ github.event.pull_request.head.repo.full_name }}" + exact_head = "ref: ${{ github.event.pull_request.head.sha }}" + + for job_name in ("trivy-fs", "scorecard"): + job = workflow_job(workflow, job_name) + assert exact_repository in job + assert exact_head in job + assert "persist-credentials: false" in job + + +def test_dependency_review_checkout_is_bound_to_the_same_exact_head() -> None: + """Supporting checkout evidence must match the API comparison head.""" + + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + job = workflow_job(workflow, "dependency-review") + + assert "repository: ${{ github.event.pull_request.head.repo.full_name }}" in job + assert "ref: ${{ github.event.pull_request.head.sha }}" in job + assert "HEAD_SHA: ${{ github.event.pull_request.head.sha }}" in job From 7d20fb714b5d3d60b3e928e1ef600d67dfec882b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 15:55:43 +0900 Subject: [PATCH 02/11] ci(security): execute exact-head scanner contract --- .../security-scan-exact-head-quality-ci.yml | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/security-scan-exact-head-quality-ci.yml diff --git a/.github/workflows/security-scan-exact-head-quality-ci.yml b/.github/workflows/security-scan-exact-head-quality-ci.yml new file mode 100644 index 000000000..67169bf7e --- /dev/null +++ b/.github/workflows/security-scan-exact-head-quality-ci.yml @@ -0,0 +1,37 @@ +name: Security Scan Exact-Head Quality CI + +on: + pull_request: + paths: + - ".github/workflows/security-scan.yml" + - ".github/workflows/security-scan-exact-head-quality-ci.yml" + - "tests/test_security_scan_exact_head.py" + +concurrency: + group: security-scan-exact-head-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + exact-head-contract: + runs-on: ubuntu-24.04 + steps: + - name: Checkout literal pull request head + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + - name: Compile exact-head contract + run: python3 -m py_compile tests/test_security_scan_exact_head.py + - name: Execute dependency-free exact-head contract + run: | + python3 - <<'PY' + from tests import test_security_scan_exact_head as contract + + contract.test_repository_scanners_checkout_the_literal_pull_request_head() + contract.test_dependency_review_checkout_is_bound_to_the_same_exact_head() + print("security scan exact-head contract passed") + PY From e75172d31bdc0eefa364254ddf70af06d600f9f4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 15:56:47 +0900 Subject: [PATCH 03/11] test(security): require literal-head SARIF attribution --- tests/test_security_scan_sarif_exact_head.py | 40 ++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/test_security_scan_sarif_exact_head.py diff --git a/tests/test_security_scan_sarif_exact_head.py b/tests/test_security_scan_sarif_exact_head.py new file mode 100644 index 000000000..78f3e8bc3 --- /dev/null +++ b/tests/test_security_scan_sarif_exact_head.py @@ -0,0 +1,40 @@ +"""Durable exact-head SARIF contracts for central repository scanners.""" + +from pathlib import Path + + +WORKFLOW_PATH = ( + Path(__file__).resolve().parents[1] + / ".github" + / "workflows" + / "security-scan.yml" +) + + +def _job_block(workflow: str, job_name: str) -> str: + """Return one two-space-indented GitHub Actions job block.""" + + normalized = workflow.replace("\r\n", "\n").replace("\r", "\n") + marker = f"\n {job_name}:\n" + start = normalized.index(marker) + len(marker) + remaining = normalized[start:] + offset = 0 + for line in remaining.splitlines(keepends=True): + if offset and line.startswith(" ") and not line.startswith(" "): + if line.rstrip().endswith(":"): + return remaining[:offset] + offset += len(line) + return remaining + + +def test_repository_scanner_sarif_is_attributed_to_the_literal_head() -> None: + """Trivy and Scorecard SARIF must identify the exact scanned head SHA.""" + + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + expected_ref = "ref: refs/pull/${{ github.event.pull_request.number }}/head" + expected_sha = "sha: ${{ github.event.pull_request.head.sha }}" + + for job_name in ("trivy-fs", "scorecard"): + job = _job_block(workflow, job_name) + assert expected_ref in job + assert expected_sha in job From a7028f2ebe46921983d63ef460f6ae5283668ca0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 15:58:04 +0900 Subject: [PATCH 04/11] fix(security): scan and publish literal PR-head evidence --- .github/workflows/security-scan.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index c3b8fa5db..4314ad3f1 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -257,9 +257,11 @@ jobs: contents: read pull-requests: read steps: - - name: Checkout + - name: Checkout exact head uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false - name: Check dependency review support id: dependency_review_support @@ -311,9 +313,11 @@ jobs: security-events: write actions: read steps: - - name: Checkout + - name: Checkout exact head uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false - name: Trivy filesystem scan uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 @@ -387,6 +391,8 @@ jobs: with: sarif_file: trivy-results.sarif category: trivy-fs + ref: refs/pull/${{ github.event.pull_request.number }}/head + sha: ${{ github.event.pull_request.head.sha }} wait-for-processing: false - name: Report Trivy SARIF upload failure if: steps.upload_trivy_sarif.outcome == 'failure' @@ -403,9 +409,11 @@ jobs: contents: read actions: read steps: - - name: Checkout + - name: Checkout exact head uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false - name: Run Scorecard uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 @@ -461,6 +469,8 @@ jobs: with: sarif_file: results.sarif category: scorecard + ref: refs/pull/${{ github.event.pull_request.number }}/head + sha: ${{ github.event.pull_request.head.sha }} wait-for-processing: false - name: Report Scorecard SARIF upload failure if: steps.upload_scorecard_sarif.outcome == 'failure' From 115b2fc7fe97f3ddac60ede138d1c684828c9db2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 15:58:31 +0900 Subject: [PATCH 05/11] ci(security): verify exact-head checkout and SARIF contracts --- .../security-scan-exact-head-quality-ci.yml | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/security-scan-exact-head-quality-ci.yml b/.github/workflows/security-scan-exact-head-quality-ci.yml index 67169bf7e..e0b30fb16 100644 --- a/.github/workflows/security-scan-exact-head-quality-ci.yml +++ b/.github/workflows/security-scan-exact-head-quality-ci.yml @@ -6,6 +6,7 @@ on: - ".github/workflows/security-scan.yml" - ".github/workflows/security-scan-exact-head-quality-ci.yml" - "tests/test_security_scan_exact_head.py" + - "tests/test_security_scan_sarif_exact_head.py" concurrency: group: security-scan-exact-head-${{ github.event.pull_request.number }} @@ -24,14 +25,19 @@ jobs: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false - - name: Compile exact-head contract - run: python3 -m py_compile tests/test_security_scan_exact_head.py - - name: Execute dependency-free exact-head contract + - name: Compile exact-head contracts + run: >- + python3 -m py_compile + tests/test_security_scan_exact_head.py + tests/test_security_scan_sarif_exact_head.py + - name: Execute dependency-free exact-head contracts run: | python3 - <<'PY' - from tests import test_security_scan_exact_head as contract + from tests import test_security_scan_exact_head as checkout_contract + from tests import test_security_scan_sarif_exact_head as sarif_contract - contract.test_repository_scanners_checkout_the_literal_pull_request_head() - contract.test_dependency_review_checkout_is_bound_to_the_same_exact_head() - print("security scan exact-head contract passed") + checkout_contract.test_repository_scanners_checkout_the_literal_pull_request_head() + checkout_contract.test_dependency_review_checkout_is_bound_to_the_same_exact_head() + sarif_contract.test_repository_scanner_sarif_is_attributed_to_the_literal_head() + print("security scan exact-head contracts passed") PY From fc37844e5b69c10ebd86e575ef5112edd341ede8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 15:59:10 +0900 Subject: [PATCH 06/11] docs(security): record literal-head scanner evidence --- docs/doctoring/security-scan-exact-head.md | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 docs/doctoring/security-scan-exact-head.md diff --git a/docs/doctoring/security-scan-exact-head.md b/docs/doctoring/security-scan-exact-head.md new file mode 100644 index 000000000..dedad3a41 --- /dev/null +++ b/docs/doctoring/security-scan-exact-head.md @@ -0,0 +1,49 @@ +# Security scan exact-head evidence + +## Decision + +The central `Security Scan` workflow treats the literal pull-request head as the only valid repository-scanner input. GitHub's `pull_request` event normally exposes a generated merge revision through `GITHUB_SHA`; that revision is useful for integration testing but cannot prove that Trivy or Scorecard scanned the exact current contributor head required by CWL authorization policy. + +The dependency-review support checkout, Trivy filesystem scan, and Scorecard posture scan therefore set both: + +```yaml +repository: ${{ github.event.pull_request.head.repo.full_name }} +ref: ${{ github.event.pull_request.head.sha }} +``` + +Persisted checkout credentials remain disabled. Fork pull requests are read through their explicit head repository and immutable commit SHA; no write credential is added. + +## Durable SARIF identity + +Scanning the head is insufficient when durable code-scanning evidence is attributed to a different revision. Trivy and Scorecard uploads explicitly bind: + +```yaml +ref: refs/pull/${{ github.event.pull_request.number }}/head +sha: ${{ github.event.pull_request.head.sha }} +``` + +GitHub's code-scanning API requires both a full Git reference and the commit SHA to which an uploaded analysis relates. The pair above states that the SARIF describes the pull-request head, not the generated merge commit. + +## Preserved security behavior + +This change does not alter scanner versions, vulnerability severities, Trivy's fixable Medium-or-higher hard gate, dependency-review thresholds, Scorecard's soft posture role, SARIF sanitation, permissions, or the existing OSV base-versus-head comparison. It only makes scanner input and result identity consistent. + +The workflow remains fail closed for absent scanner output and actionable findings. SARIF upload failures remain separately visible without suppressing the repository-local Trivy finding gate. A queued, cancelled, skipped, failed, missing, or predecessor-head run is not current-head evidence. + +## Verification + +`tests/test_security_scan_exact_head.py` verifies literal-head checkout for all three affected jobs. `tests/test_security_scan_sarif_exact_head.py` verifies durable Trivy and Scorecard SARIF attribution. The dedicated read-only quality workflow checks out the literal PR head, compiles both contracts, and executes them without package installation. + +The initiating DiskSage evidence was Security Scan run `31070907732`, whose Trivy job log checked out `refs/remotes/pull/137/merge` rather than DiskSage PR #137 head `87ac0e08cceed3d1a766da13a8f8123912178192`. That result remains historical merge-tree evidence and is not reclassified as exact-head proof. + +## Rollback + +Rollback requires an independently reviewed revert and fresh exact-head security evidence. Do not restore implicit checkout or automatic SARIF revision detection unless an equally strict mechanism proves that the scanned filesystem, SARIF `ref`, and SARIF `sha` all identify the same current pull-request head. + +## APA 7th references + +GitHub. (n.d.). *Events that trigger workflows*. GitHub Docs. Retrieved August 6, 2026, from https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows + +GitHub. (n.d.). *REST API endpoints for code scanning*. GitHub Docs. Retrieved August 6, 2026, from https://docs.github.com/en/rest/code-scanning/code-scanning + +GitHub. (n.d.). *Uploading CodeQL analysis results to GitHub*. GitHub Docs. Retrieved August 6, 2026, from https://docs.github.com/en/enterprise-cloud@latest/code-security/tutorials/customize-code-scanning/upload-results From 323c07b794d11f82c04db91544bc3a3f5cf5ad5c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 15:59:29 +0900 Subject: [PATCH 07/11] docs: record exact-head security scanner repair --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e601de81b..9b4e84e9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,5 +12,6 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Bound dependency-review support, Trivy, and Scorecard checkouts to the literal pull-request head repository and SHA; bound Trivy and Scorecard SARIF uploads to the matching `refs/pull//head` identity; and added permanent dependency-free exact-head regression evidence. - Bound both trusted-uv quality jobs to `github.event.pull_request.head.sha` and added a permanent two-checkout regression contract so exact-head compatibility, coverage, docstring, and compilation claims cannot silently measure GitHub's generated pull-request merge revision. - Made Strix treat only a single LiteLLM provider-error line containing NVIDIA NIM context and model-catalog 404 evidence as cross-model fallback evidence, rejecting cross-line signal assembly and provider-like target source literals; moved the public default to Nemotron 3 Super 120B and added a second NVIDIA hosted candidate before GitHub Models without neutralizing reported vulnerabilities. From 682088b60f779f72935cb89d4610ec17b7a2c5c5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 12:17:59 +0900 Subject: [PATCH 08/11] test(security): prove unavailable dependency review fails closed --- tests/test_security_scan_exact_head.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_security_scan_exact_head.py b/tests/test_security_scan_exact_head.py index 3b6e34a57..654702d3d 100644 --- a/tests/test_security_scan_exact_head.py +++ b/tests/test_security_scan_exact_head.py @@ -55,3 +55,19 @@ def test_dependency_review_checkout_is_bound_to_the_same_exact_head() -> None: assert "repository: ${{ github.event.pull_request.head.repo.full_name }}" in job assert "ref: ${{ github.event.pull_request.head.sha }}" in job assert "HEAD_SHA: ${{ github.event.pull_request.head.sha }}" in job + + +def test_dependency_review_support_probe_fails_closed_unless_api_returns_200() -> None: + """Unavailable dependency-review evidence must never become a green gate.""" + + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + job = workflow_job(workflow, "dependency-review") + + assert 'if [ "$status" != "200" ]; then' in job + assert "supported=false" not in job + assert "skipping dependency-review hard gate" not in job + assert 'cat "$response_file"' not in job + assert "${REPOSITORY}" in job + assert "${BASE_SHA}" in job + assert "${HEAD_SHA}" in job + assert "HTTP ${status}" in job From 2d1603f1c307be83a12d8b2f847d6a91b1ce97b3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 12:19:23 +0900 Subject: [PATCH 09/11] test(security): execute dependency-review fail-closed contract --- .github/workflows/security-scan-exact-head-quality-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/security-scan-exact-head-quality-ci.yml b/.github/workflows/security-scan-exact-head-quality-ci.yml index e0b30fb16..8c84ecceb 100644 --- a/.github/workflows/security-scan-exact-head-quality-ci.yml +++ b/.github/workflows/security-scan-exact-head-quality-ci.yml @@ -38,6 +38,7 @@ jobs: checkout_contract.test_repository_scanners_checkout_the_literal_pull_request_head() checkout_contract.test_dependency_review_checkout_is_bound_to_the_same_exact_head() + checkout_contract.test_dependency_review_support_probe_fails_closed_unless_api_returns_200() sarif_contract.test_repository_scanner_sarif_is_attributed_to_the_literal_head() print("security scan exact-head contracts passed") PY From 7c0b6f9ffb7bc1c6364df3101879191648302210 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 12:22:35 +0900 Subject: [PATCH 10/11] fix(security): fail closed on unavailable dependency review --- .github/workflows/security-scan.yml | 30 +++++++++++------------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 4314ad3f1..f12454d46 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -16,10 +16,11 @@ # pull_request workflows upload to refs/pull/N/merge, so no single ref ever holds # all tools. Bundling at the workflow/check level is ref-independent. # -# NOTE on dependency-review: dependency graph can be unavailable on some repos. -# Treat that as "not enforceable here" instead of making the required workflow -# unsatisfiable; keep medium-or-higher dependency findings hard-failing where the -# API is supported. +# NOTE on dependency-review: for this organization-owned hard gate, unavailable +# dependency-review evidence is not a clean result. Only an exact base/head API +# comparison that returns HTTP 200 may proceed to the pinned dependency-review +# action; every other support-probe outcome fails closed without printing the +# untrusted API response body. # # NOTE on trivy-fs: it scans the whole repo, so a pre-existing FIXABLE # MEDIUM/HIGH/CRITICAL finding blocks every PR in that repo until it is fixed. @@ -274,9 +275,8 @@ jobs: set -euo pipefail api_url="${GITHUB_API_URL:-https://api.github.com}" - response_file="$(mktemp)" status="$( - curl -fsS -o "$response_file" -w '%{http_code}' \ + curl -sS -o /dev/null -w '%{http_code}' \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer ${GH_TOKEN}" \ -H "X-GitHub-Api-Version: 2022-11-28" \ @@ -284,20 +284,12 @@ jobs: || true )" - if [ "$status" = "200" ]; then - echo "supported=true" >>"$GITHUB_OUTPUT" - exit 0 - fi - - if [ "$status" = "403" ] || [ "$status" = "404" ]; then - echo "::warning::Dependency review is unavailable for ${REPOSITORY}; skipping dependency-review hard gate." - echo "supported=false" >>"$GITHUB_OUTPUT" - exit 0 + if [ "$status" != "200" ]; then + echo "::error::Dependency review evidence unavailable for ${REPOSITORY} at exact base ${BASE_SHA} and head ${HEAD_SHA}: HTTP ${status:-unavailable}. Failing closed." + exit 1 fi - echo "::error::Dependency review support check failed with HTTP ${status}." - cat "$response_file" - exit 1 + echo "supported=true" >>"$GITHUB_OUTPUT" - name: Dependency review if: steps.dependency_review_support.outputs.supported == 'true' uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 @@ -475,4 +467,4 @@ jobs: - name: Report Scorecard SARIF upload failure if: steps.upload_scorecard_sarif.outcome == 'failure' run: | - echo "::warning::Scorecard SARIF upload to code scanning failed after delegated PR-only findings were filtered. Scorecard is PR posture evidence only; CodeQL, OSV, Trivy, and dependency-review remain the hard gates." + echo "::warning::Scorecard SARIF upload to code scanning failed after delegated PR-only findings were filtered. Scorecard is PR posture evidence only; CodeQL, OSV, Trivy, and dependency-review remain the hard gates." \ No newline at end of file From 3c1653a5c26aca930ac175c19cd8cf53f9ed62d7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 12:23:21 +0900 Subject: [PATCH 11/11] docs(security): document fail-closed dependency review evidence --- docs/doctoring/security-scan-exact-head.md | 28 +++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/doctoring/security-scan-exact-head.md b/docs/doctoring/security-scan-exact-head.md index dedad3a41..6c3d41d11 100644 --- a/docs/doctoring/security-scan-exact-head.md +++ b/docs/doctoring/security-scan-exact-head.md @@ -13,6 +13,20 @@ ref: ${{ github.event.pull_request.head.sha }} Persisted checkout credentials remain disabled. Fork pull requests are read through their explicit head repository and immutable commit SHA; no write credential is added. +## Dependency-review availability is evidence, not an optimization + +Dependency review is a hard supply-chain gate. The support probe compares the exact pull-request base SHA with the exact pull-request head SHA through GitHub's dependency-review API. Only HTTP `200` is accepted as evidence that the pinned `actions/dependency-review-action` may execute. HTTP `403`, `404`, `000`, an empty or malformed status, a transport failure, timeout, or any other unexpected probe result is **not** a clean dependency review and fails the job closed. + +The failure diagnostic records only the repository identifier, exact base SHA, exact head SHA, and HTTP status. The API response body is discarded rather than printed because it is unnecessary for the authorization decision and can contain operational details that do not belong in a public workflow log. Authentication material is never included in the diagnostic. + +A dependency-neutral path classifier is not a substitute for dependency-review evidence. In particular, the workflow must not translate an unavailable API into `not-applicable` merely because another mechanism believes the current diff contains no dependency change. OSV, Trivy, CodeQL, Semgrep, Secret Scan, Scorecard, and Dependabot remain independent controls; none semantically replaces the dependency-diff gate. + +## Operator remediation for an unavailable gate + +For a public GitHub.com repository, a `403` or `404` from the dependency-review comparison endpoint is treated as a repository or organization configuration problem until evidence proves otherwise. An operator should verify that the dependency graph and the GitHub security features required for dependency review are enabled for the repository and organization, that organization policy permits the endpoint, and that the workflow's read-only token receives the documented access needed by the dependency-review API and action. Rerun only after the capability or policy path is corrected; do not weaken the workflow to manufacture a green check. + +Private or internal repositories can have different product-entitlement and policy requirements. Any exception for those repository classes must be designed as an explicit organization policy with independently reviewable entitlement evidence. It must not be inferred from a failed probe and must not weaken the public-repository canary semantics. + ## Durable SARIF identity Scanning the head is insufficient when durable code-scanning evidence is attributed to a different revision. Trivy and Scorecard uploads explicitly bind: @@ -26,22 +40,30 @@ GitHub's code-scanning API requires both a full Git reference and the commit SHA ## Preserved security behavior -This change does not alter scanner versions, vulnerability severities, Trivy's fixable Medium-or-higher hard gate, dependency-review thresholds, Scorecard's soft posture role, SARIF sanitation, permissions, or the existing OSV base-versus-head comparison. It only makes scanner input and result identity consistent. +This change does not alter scanner versions, vulnerability severities, Trivy's fixable Medium-or-higher hard gate, dependency-review thresholds, Scorecard's soft posture role, SARIF sanitation, permissions, or the existing OSV base-versus-head comparison. It makes scanner input and result identity consistent and makes unavailable dependency-review evidence an explicit hard failure instead of a green skip. The workflow remains fail closed for absent scanner output and actionable findings. SARIF upload failures remain separately visible without suppressing the repository-local Trivy finding gate. A queued, cancelled, skipped, failed, missing, or predecessor-head run is not current-head evidence. ## Verification -`tests/test_security_scan_exact_head.py` verifies literal-head checkout for all three affected jobs. `tests/test_security_scan_sarif_exact_head.py` verifies durable Trivy and Scorecard SARIF attribution. The dedicated read-only quality workflow checks out the literal PR head, compiles both contracts, and executes them without package installation. +`tests/test_security_scan_exact_head.py` verifies literal-head checkout and the rule that only an HTTP `200` support probe may reach dependency review. It also rejects the former `supported=false` / skip path and response-body logging. `tests/test_security_scan_sarif_exact_head.py` verifies durable Trivy and Scorecard SARIF attribution. The dedicated read-only quality workflow checks out the literal PR head, compiles both contracts, and executes them without package installation. The initiating DiskSage evidence was Security Scan run `31070907732`, whose Trivy job log checked out `refs/remotes/pull/137/merge` rather than DiskSage PR #137 head `87ac0e08cceed3d1a766da13a8f8123912178192`. That result remains historical merge-tree evidence and is not reclassified as exact-head proof. +The dependency-review availability regression was reproduced on the public EgressWeave canary: a support probe returned HTTP `403`, the former workflow marked the hard action skipped, and the aggregate Security Scan still concluded success. That historical result is unavailable dependency-review evidence, not proof of a clean dependency diff. + ## Rollback -Rollback requires an independently reviewed revert and fresh exact-head security evidence. Do not restore implicit checkout or automatic SARIF revision detection unless an equally strict mechanism proves that the scanned filesystem, SARIF `ref`, and SARIF `sha` all identify the same current pull-request head. +Rollback requires an independently reviewed revert and fresh exact-head security evidence. Do not restore implicit checkout, automatic SARIF revision detection, or a fail-open dependency-review support path unless an equally strict mechanism proves the same authorization properties. In particular, never convert `403`, `404`, transport failure, or another unavailable probe outcome into a successful hard gate. ## APA 7th references +GitHub. (n.d.). *Dependency review*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/code-security/concepts/supply-chain-security/dependency-review + +GitHub. (n.d.). *REST API endpoints for dependency review*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/enterprise-cloud@latest/rest/dependency-graph/dependency-review + +GitHub. (n.d.). *Customizing your dependency review action configuration*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/code-security/tutorials/secure-your-dependencies/customize-dependency-review-action + GitHub. (n.d.). *Events that trigger workflows*. GitHub Docs. Retrieved August 6, 2026, from https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows GitHub. (n.d.). *REST API endpoints for code scanning*. GitHub Docs. Retrieved August 6, 2026, from https://docs.github.com/en/rest/code-scanning/code-scanning