From 8e93ae226b52a4d0456137ae36191241d5c58fe7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 19 Sep 2026 04:09:30 +0900 Subject: [PATCH 1/7] test(codeql): reproduce GHAS analysis credential masking --- ..._scan_dispatch_ghas_credential_contract.py | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 tests/test_codeql_scan_dispatch_ghas_credential_contract.py diff --git a/tests/test_codeql_scan_dispatch_ghas_credential_contract.py b/tests/test_codeql_scan_dispatch_ghas_credential_contract.py new file mode 100644 index 0000000000..1d8856ffe6 --- /dev/null +++ b/tests/test_codeql_scan_dispatch_ghas_credential_contract.py @@ -0,0 +1,106 @@ +"""Credential-routing contract for cross-repository GHAS CodeQL analysis reads.""" + +from __future__ import annotations + +import os +import shutil +import subprocess +from pathlib import Path + +from tests.test_opencode_workflow_shell_syntax import _extract_run_block + + +REPO_ROOT = Path(__file__).resolve().parents[1] +WORKFLOW_PATH = REPO_ROOT / ".github/workflows/codeql-scan-dispatch.yml" +SELECT_STEP_NAME = "Select target CodeQL analysis-read credential" +VERIFY_STEP_NAME = "Verify GHAS base/head CodeQL configuration identity" + + +def _run_selector(tmp_path: Path, *, succeeding_token: str | None) -> subprocess.CompletedProcess[str]: + bash = shutil.which("bash") + assert bash is not None, "bash is required to run this test" + + workflow_text = WORKFLOW_PATH.read_text(encoding="utf-8") + script = _extract_run_block(workflow_text, SELECT_STEP_NAME) + + fake_bin = tmp_path / "bin" + fake_bin.mkdir(parents=True) + call_log = tmp_path / "calls" + fake_gh = fake_bin / "gh" + fake_gh.write_text( + "#!/usr/bin/env bash\n" + "set -euo pipefail\n" + 'printf \'%s\\n\' "${GH_TOKEN:-}" >>"$FAKE_CALL_LOG"\n' + 'test "$1" = api\n' + 'if [ -n "${SUCCEEDING_TOKEN:-}" ] && [ "${GH_TOKEN:-}" = "$SUCCEEDING_TOKEN" ]; then\n' + " printf '[]\\n'\n" + " exit 0\n" + "fi\n" + "exit 1\n", + encoding="utf-8", + ) + fake_gh.chmod(0o755) + + output = tmp_path / "github-output" + env = { + **os.environ, + "PATH": f"{fake_bin}:{os.environ['PATH']}", + "GITHUB_OUTPUT": str(output), + "FAKE_CALL_LOG": str(call_log), + "SUCCEEDING_TOKEN": succeeding_token or "", + "TARGET_REPOSITORY": "ContextualWisdomLab/OriginWeave", + "TARGET_APP_TOKEN": "content-token", + "PR_REVIEW_MERGE_TOKEN": "security-token", + "OPENCODE_APPROVE_TOKEN": "approve-token", + "WORKFLOW_TOKEN": "workflow-token", + } + result = subprocess.run( + [bash], + input=script, + text=True, + capture_output=True, + check=False, + env=env, + ) + result.output_path = output # type: ignore[attr-defined] + result.call_log = call_log # type: ignore[attr-defined] + return result + + +def test_ghas_analysis_read_falls_through_content_only_target_app_token(tmp_path: Path) -> None: + """A content-capable app token must not mask a later GHAS-capable credential.""" + result = _run_selector(tmp_path, succeeding_token="security-token") + + assert result.returncode == 0, result.stdout + result.stderr + output = result.output_path.read_text(encoding="utf-8") + assert "token=security-token" in output + assert "source=pr-review-merge-token" in output + assert result.call_log.read_text(encoding="utf-8").splitlines() == [ + "content-token", + "security-token", + ] + + +def test_ghas_analysis_read_fails_closed_when_no_candidate_can_read_target(tmp_path: Path) -> None: + """Missing target code-scanning read authority must remain a hard prerequisite failure.""" + result = _run_selector(tmp_path, succeeding_token=None) + + assert result.returncode != 0 + assert "no configured credential can read target CodeQL analyses" in result.stderr + assert result.call_log.read_text(encoding="utf-8").splitlines() == [ + "content-token", + "security-token", + "approve-token", + "workflow-token", + ] + + +def test_ghas_identity_step_consumes_only_probed_analysis_read_token() -> None: + """The identity proof must not repeat the unprobed content-token precedence chain.""" + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + verify_script = _extract_run_block(workflow, VERIFY_STEP_NAME) + verify_prefix = workflow.split(f" - name: {VERIFY_STEP_NAME}\n", 1)[1].split(" run: |", 1)[0] + + assert "GH_TOKEN: ${{ steps.ghas_analysis_token.outputs.token }}" in verify_prefix + assert "steps.target_app_token.outputs.token ||" not in verify_prefix + assert "codeql_ghas_configuration_identity.py" in verify_script From 4c4fff284e6bb58fe738389a647e2a7d1031dd54 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 19 Sep 2026 04:12:26 +0900 Subject: [PATCH 2/7] fix(codeql): prove target GHAS analysis-read credential --- .github/workflows/codeql-scan-dispatch.yml | 47 +++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql-scan-dispatch.yml b/.github/workflows/codeql-scan-dispatch.yml index e2c41fe339..b34ea29fc0 100644 --- a/.github/workflows/codeql-scan-dispatch.yml +++ b/.github/workflows/codeql-scan-dispatch.yml @@ -589,11 +589,56 @@ jobs: id: gate run: python3 "$RUNNER_TEMP/codeql_sarif_gate.py" codeql-results-dispatch + - name: Select target CodeQL analysis-read credential + id: ghas_analysis_token + if: steps.gate.outcome == 'success' + env: + TARGET_REPOSITORY: ${{ needs.validate-dispatch.outputs.target_repository }} + TARGET_APP_TOKEN: ${{ steps.target_app_token.outputs.token || '' }} + PR_REVIEW_MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || '' }} + OPENCODE_APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || '' }} + WORKFLOW_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + probe_analysis_read() { + token_label="$1" + token="$2" + if [ -z "$token" ]; then + return 1 + fi + if GH_TOKEN="$token" gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "repos/${TARGET_REPOSITORY}/code-scanning/analyses?per_page=1&tool_name=CodeQL" \ + >/dev/null 2>&1; then + echo "::add-mask::$token" + { + printf 'token=%s\n' "$token" + printf 'source=%s\n' "$token_label" + } >>"$GITHUB_OUTPUT" + echo "Selected ${token_label} after proving target CodeQL analysis-read access." + return 0 + fi + echo "::notice::${token_label} cannot read target CodeQL analyses; trying the next configured credential." + return 1 + } + + if probe_analysis_read "target-app-token" "$TARGET_APP_TOKEN" || + probe_analysis_read "pr-review-merge-token" "$PR_REVIEW_MERGE_TOKEN" || + probe_analysis_read "opencode-approve-token" "$OPENCODE_APPROVE_TOKEN" || + probe_analysis_read "github-token" "$WORKFLOW_TOKEN"; then + exit 0 + fi + + echo "::error::no configured credential can read target CodeQL analyses; GHAS configuration identity cannot be proven." + exit 1 + - name: Verify GHAS base/head CodeQL configuration identity id: ghas_configuration_identity if: steps.gate.outcome == 'success' env: - GH_TOKEN: ${{ steps.target_app_token.outputs.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }} + GH_TOKEN: ${{ steps.ghas_analysis_token.outputs.token }} TARGET_REPOSITORY: ${{ needs.validate-dispatch.outputs.target_repository }} PR_NUMBER: ${{ needs.validate-dispatch.outputs.pr_number }} BASE_REF: ${{ needs.validate-dispatch.outputs.base_ref }} From 443c9f6013a831b7e4605532809f7aae375d1019 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 19 Sep 2026 04:12:54 +0900 Subject: [PATCH 3/7] docs(codeql): doctor GHAS analysis credential masking --- ...-analysis-credential-routing-2026-09-19.md | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 docs/doctoring/codeql-ghas-analysis-credential-routing-2026-09-19.md diff --git a/docs/doctoring/codeql-ghas-analysis-credential-routing-2026-09-19.md b/docs/doctoring/codeql-ghas-analysis-credential-routing-2026-09-19.md new file mode 100644 index 0000000000..2383f7c731 --- /dev/null +++ b/docs/doctoring/codeql-ghas-analysis-credential-routing-2026-09-19.md @@ -0,0 +1,73 @@ +# CodeQL GHAS analysis credential routing — 2026-09-19 + +## Symptom + +OriginWeave PR #229 dispatch run `35303205858` reached the central scan jobs after `validate-dispatch` succeeded. The `actions` job `105600898203` and `javascript-typescript` job `105600898234` both completed CodeQL analysis and the Medium+ SARIF gate successfully, then failed specifically at `Verify GHAS base/head CodeQL configuration identity`. + +The identity step failed in about one second in both jobs. That is materially different from the bounded identity-continuity wait: `codeql_ghas_configuration_identity.py` polls up to 30 times with a 20-second interval when a legitimate base identity is merely not present on the exact head yet. An immediate failure therefore indicates that the analyses API could not be read, rather than that the intended continuity poll exhausted its budget. + +The Python shard `105600898461` remained queued at the time this repair lane was opened. + +## Root cause + +The scan job exchanges an OpenCode GitHub App token for target repository **content reads** and uses it successfully for live pull-request metadata and head materialization. The GHAS identity step reused this precedence expression: + +```text +steps.target_app_token.outputs.token || PR_REVIEW_MERGE_TOKEN || OPENCODE_APPROVE_TOKEN || github.token +``` + +GitHub expression fallback is based on token presence, not API capability. A non-empty content-capable target token therefore masked every later credential even when it could not read the target repository's `code-scanning/analyses` endpoint. The job-level `security-events: read` permission applies to the workflow repository's `github.token`; it does not by itself grant that token cross-repository GHAS access. + +This is a credential-capability selection defect, not a CodeQL analysis defect: both failed shards had successful `Perform CodeQL Analysis`, successful Medium+ SARIF gates, and preserved SARIF evidence before the identity-read failure. + +## Ownership and stacking + +The canonical repair is central because cross-repository CodeQL evidence identity is owned by `ContextualWisdomLab/.github`. It is intentionally stacked on PR #2271 (`fix/codeql-dispatch-repository-identity`) because #2271 already changes `codeql-scan-dispatch.yml`; the successor preserves that admission repair instead of opening a conflicting parallel writer against `main`. + +OriginWeave must not copy this workflow or synthesize a success status. + +## RED + +Commit `8e93ae226b52a4d0456137ae36191241d5c58fe7` adds an executable shell contract that requires: + +- a content-only first credential to fall through when the target analyses endpoint rejects it; +- the next credential with proven target CodeQL analysis-read access to be selected; +- all configured candidates failing to read the endpoint to remain a hard failure; +- the GHAS identity step to consume only the credential that passed the capability probe, rather than repeating the presence-only precedence chain. + +The test executes the workflow's real selector `run:` block with a fake `gh` boundary; it does not duplicate the production selection algorithm in Python. + +## Repair + +Commit `4c4fff284e6bb58fe738389a647e2a7d1031dd54` adds one preflight step immediately after the Medium+ SARIF gate. It probes the exact target repository's CodeQL analyses endpoint with the already-configured credentials in existing precedence order: + +1. exchanged target app token; +2. `PR_REVIEW_MERGE_TOKEN`; +3. `OPENCODE_APPROVE_TOKEN`; +4. workflow token. + +The first credential that **actually succeeds** against `repos//code-scanning/analyses` is masked and passed as a step output to the identity verifier. If no configured credential can read that endpoint, the job fails closed with an explicit capability diagnostic. No permission is broadened, no secret is printed, and the GHAS identity proof itself is unchanged. + +Compared with #2271 exact `2b849c874122961e025c29f7fa0bb697863c3d68`, the repair generation is ordinary-forward, ahead by two commits, and changes only the CodeQL dispatch workflow plus its focused credential-routing contract. + +## Alternatives rejected + +Using the first non-empty token remained rejected because that is the defect reproduced by the live run. + +Skipping GHAS identity verification when the analyses API is unreadable was rejected because dispatch SARIF cannot impersonate GitHub Default setup configuration identity. The existing #2133 invariant remains mandatory. + +Granting broader permissions blindly to the exchanged token was not assumed. The exchange endpoint currently returns an installation token without a workflow-side permission request contract. Capability is therefore proven at the target endpoint rather than inferred from the token's provenance or label. + +Retrying the failed OriginWeave head before the central owner repair is accepted was rejected: it would repeat the same deterministic credential selection and create noise rather than new evidence. + +## Acceptance + +Before merge, the stacked successor needs: + +- focused RED→GREEN contract execution; +- existing CodeQL dispatch workflow/shell contracts; +- exact-head hosted security/quality checks; +- independent review; +- an end-to-end dispatch proving that the selected credential can read target GHAS analyses and that the existing base/head identity proof reaches its normal terminal verdict. + +Only after the central repair is accepted should OriginWeave #229 receive a normal exact-head CodeQL rerun. No predecessor run, SARIF artifact, review, or status is transferable to a changed successor head. From a900ec17f1e4a7db384e3052c23d3cf7440f0651 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 19 Sep 2026 05:02:54 +0900 Subject: [PATCH 4/7] test(codeql): assert fail-closed selector stdout --- tests/test_codeql_scan_dispatch_ghas_credential_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_codeql_scan_dispatch_ghas_credential_contract.py b/tests/test_codeql_scan_dispatch_ghas_credential_contract.py index 1d8856ffe6..b4139f8621 100644 --- a/tests/test_codeql_scan_dispatch_ghas_credential_contract.py +++ b/tests/test_codeql_scan_dispatch_ghas_credential_contract.py @@ -86,7 +86,7 @@ def test_ghas_analysis_read_fails_closed_when_no_candidate_can_read_target(tmp_p result = _run_selector(tmp_path, succeeding_token=None) assert result.returncode != 0 - assert "no configured credential can read target CodeQL analyses" in result.stderr + assert "no configured credential can read target CodeQL analyses" in result.stdout assert result.call_log.read_text(encoding="utf-8").splitlines() == [ "content-token", "security-token", From a3b8cc1017acb82ed9a002c1f591c70617ad87e6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 19 Sep 2026 05:04:08 +0900 Subject: [PATCH 5/7] docs(codeql): currentize three-shard RCA and review correction --- ...has-analysis-credential-routing-2026-09-19.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/doctoring/codeql-ghas-analysis-credential-routing-2026-09-19.md b/docs/doctoring/codeql-ghas-analysis-credential-routing-2026-09-19.md index 2383f7c731..3fef29cd18 100644 --- a/docs/doctoring/codeql-ghas-analysis-credential-routing-2026-09-19.md +++ b/docs/doctoring/codeql-ghas-analysis-credential-routing-2026-09-19.md @@ -2,11 +2,11 @@ ## Symptom -OriginWeave PR #229 dispatch run `35303205858` reached the central scan jobs after `validate-dispatch` succeeded. The `actions` job `105600898203` and `javascript-typescript` job `105600898234` both completed CodeQL analysis and the Medium+ SARIF gate successfully, then failed specifically at `Verify GHAS base/head CodeQL configuration identity`. +OriginWeave PR #229 dispatch run `35303205858` reached the central scan jobs after `validate-dispatch` succeeded. The `actions` job `105600898203`, `javascript-typescript` job `105600898234`, and Python job `105600898461` all completed CodeQL analysis and the Medium+ SARIF gate successfully, then failed specifically at `Verify GHAS base/head CodeQL configuration identity`. -The identity step failed in about one second in both jobs. That is materially different from the bounded identity-continuity wait: `codeql_ghas_configuration_identity.py` polls up to 30 times with a 20-second interval when a legitimate base identity is merely not present on the exact head yet. An immediate failure therefore indicates that the analyses API could not be read, rather than that the intended continuity poll exhausted its budget. +The identity step failed in about one second in all three shards. That is materially different from the bounded identity-continuity wait: `codeql_ghas_configuration_identity.py` polls up to 30 times with a 20-second interval when a legitimate base identity is merely not present on the exact head yet. An immediate failure therefore indicates that the analyses API could not be read, rather than that the intended continuity poll exhausted its budget. -The Python shard `105600898461` remained queued at the time this repair lane was opened. +This supersedes the earlier runner-queue diagnosis and the interim observation that the Python shard was still queued. ## Root cause @@ -18,7 +18,7 @@ steps.target_app_token.outputs.token || PR_REVIEW_MERGE_TOKEN || OPENCODE_APPROV GitHub expression fallback is based on token presence, not API capability. A non-empty content-capable target token therefore masked every later credential even when it could not read the target repository's `code-scanning/analyses` endpoint. The job-level `security-events: read` permission applies to the workflow repository's `github.token`; it does not by itself grant that token cross-repository GHAS access. -This is a credential-capability selection defect, not a CodeQL analysis defect: both failed shards had successful `Perform CodeQL Analysis`, successful Medium+ SARIF gates, and preserved SARIF evidence before the identity-read failure. +This is a credential-capability selection defect, not a CodeQL analysis defect: all three failed shards had successful `Perform CodeQL Analysis`, successful Medium+ SARIF gates, and preserved SARIF evidence before the identity-read failure. ## Ownership and stacking @@ -48,7 +48,13 @@ Commit `4c4fff284e6bb58fe738389a647e2a7d1031dd54` adds one preflight step immedi The first credential that **actually succeeds** against `repos//code-scanning/analyses` is masked and passed as a step output to the identity verifier. If no configured credential can read that endpoint, the job fails closed with an explicit capability diagnostic. No permission is broadened, no secret is printed, and the GHAS identity proof itself is unchanged. -Compared with #2271 exact `2b849c874122961e025c29f7fa0bb697863c3d68`, the repair generation is ordinary-forward, ahead by two commits, and changes only the CodeQL dispatch workflow plus its focused credential-routing contract. +Compared with #2271 exact `2b849c874122961e025c29f7fa0bb697863c3d68`, the repair generation is ordinary-forward and changes only the CodeQL dispatch workflow, focused credential-routing contract, and this doctoring record. + +## Review finding and correction + +CodeRabbit correctly found that the failure-closed contract initially inspected `result.stderr` even though the selector emits the `::error::no configured credential can read target CodeQL analyses...` diagnostic with plain `echo`, which `subprocess.run(..., capture_output=True)` captures on stdout. Commit `a900ec17f1e4a7db384e3052c23d3cf7440f0651` changes only that assertion to inspect `result.stdout`; the review thread was answered and resolved after verifying the production selector. + +A focused local execution of the exact selector semantics confirms both branches: a rejected `content-token` falls through to `security-token`, writes `token=security-token` / `source=pr-review-merge-token`, and returns zero; when every configured credential is rejected, all four candidates are attempted in order, the failure diagnostic appears on stdout, and the selector exits non-zero. This is local contract evidence only and does not replace exact-head hosted checks or the required end-to-end dispatch. ## Alternatives rejected From fc9c5537d9910f0536dab2aaf52888078c8ead64 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 19 Sep 2026 10:04:20 +0900 Subject: [PATCH 6/7] test(codeql): pin selector shell identity --- .../test_codeql_scan_dispatch_ghas_credential_contract.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_codeql_scan_dispatch_ghas_credential_contract.py b/tests/test_codeql_scan_dispatch_ghas_credential_contract.py index b4139f8621..836f256f29 100644 --- a/tests/test_codeql_scan_dispatch_ghas_credential_contract.py +++ b/tests/test_codeql_scan_dispatch_ghas_credential_contract.py @@ -3,7 +3,6 @@ from __future__ import annotations import os -import shutil import subprocess from pathlib import Path @@ -17,8 +16,8 @@ def _run_selector(tmp_path: Path, *, succeeding_token: str | None) -> subprocess.CompletedProcess[str]: - bash = shutil.which("bash") - assert bash is not None, "bash is required to run this test" + """Execute the extracted selector with fixed Bash identity and a fake ``gh`` boundary.""" + assert Path("/bin/bash").is_file(), "/bin/bash is required to run this workflow-contract test" workflow_text = WORKFLOW_PATH.read_text(encoding="utf-8") script = _extract_run_block(workflow_text, SELECT_STEP_NAME) @@ -55,7 +54,7 @@ def _run_selector(tmp_path: Path, *, succeeding_token: str | None) -> subprocess "WORKFLOW_TOKEN": "workflow-token", } result = subprocess.run( - [bash], + ["/bin/bash"], input=script, text=True, capture_output=True, From f54aeb6f5c6b30534ee2f12e040208e098957b4b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 19 Sep 2026 20:17:13 +0900 Subject: [PATCH 7/7] test(codeql): bind GHAS probe to analyses endpoint --- tests/test_codeql_scan_dispatch_ghas_credential_contract.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_codeql_scan_dispatch_ghas_credential_contract.py b/tests/test_codeql_scan_dispatch_ghas_credential_contract.py index 836f256f29..d92bf3ef2c 100644 --- a/tests/test_codeql_scan_dispatch_ghas_credential_contract.py +++ b/tests/test_codeql_scan_dispatch_ghas_credential_contract.py @@ -31,6 +31,12 @@ def _run_selector(tmp_path: Path, *, succeeding_token: str | None) -> subprocess "set -euo pipefail\n" 'printf \'%s\\n\' "${GH_TOKEN:-}" >>"$FAKE_CALL_LOG"\n' 'test "$1" = api\n' + 'test "$#" -eq 6\n' + 'test "$2" = -H\n' + 'test "$3" = "Accept: application/vnd.github+json"\n' + 'test "$4" = -H\n' + 'test "$5" = "X-GitHub-Api-Version: 2022-11-28"\n' + 'test "$6" = "repos/ContextualWisdomLab/OriginWeave/code-scanning/analyses?per_page=1&tool_name=CodeQL"\n' 'if [ -n "${SUCCEEDING_TOKEN:-}" ] && [ "${GH_TOKEN:-}" = "$SUCCEEDING_TOKEN" ]; then\n' " printf '[]\\n'\n" " exit 0\n"