Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions .github/workflows/codeql-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ jobs:
live_pr="$(gh api "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}")"
live_head="$(printf '%s' "$live_pr" | jq -r '.head.sha // empty')"
live_base="$(printf '%s' "$live_pr" | jq -r '.base.sha // empty')"
live_base_ref="$(printf '%s' "$live_pr" | jq -r '.base.ref // empty')"
live_merge="$(printf '%s' "$live_pr" | jq -r '.merge_commit_sha // empty')"
live_state="$(printf '%s' "$live_pr" | jq -r 'if (.state | type) == "string" then .state else empty end')"
if ! [[ "$PR_HEAD_SHA" =~ ^[0-9a-fA-F]{40}$ && "$live_head" =~ ^[0-9a-fA-F]{40}$ ]] || [[ "$live_state" != "open" && "$live_state" != "closed" ]]; then
Expand Down Expand Up @@ -352,6 +353,14 @@ jobs:
exit 1
fi

# PR base.sha may be a stale snapshot; authenticate the current named branch.
base_ref_path="$(jq -nr --arg ref "$live_base_ref" '$ref | @uri')"
base_ref_json="$(gh api "repos/${TARGET_REPOSITORY}/git/ref/heads/${base_ref_path}")"
live_base="$(jq -er --arg ref "refs/heads/${live_base_ref}" '
select(type == "object" and .ref == $ref and .object.type == "commit")
| .object.sha | select(type == "string" and test("^[0-9a-fA-F]{40}$"))
' <<<"$base_ref_json")"

statuses="$(gh api "repos/${TARGET_REPOSITORY}/commits/${PR_HEAD_SHA}/statuses")"
expected_context="codeql-dispatch/${LANGUAGE}/${live_base}"
expected_description="cwl1;h=${PR_HEAD_SHA};w=codeql-scan-dispatch;r=${REQUIRED_RUN_ID};s=${live_merge}"
Expand Down Expand Up @@ -718,6 +727,14 @@ jobs:
)"
done < <(printf '%s' "$include_json" | jq -c '.[]')

# PR base.sha may be a stale snapshot; authenticate the current named branch.
base_ref_path="$(jq -nr --arg ref "$live_base_ref" '$ref | @uri')"
base_ref_json="$(gh api "repos/${TARGET_REPOSITORY}/git/ref/heads/${base_ref_path}")"
live_base="$(jq -er --arg ref "refs/heads/${live_base_ref}" '
select(type == "object" and .ref == $ref and .object.type == "commit")
| .object.sha | select(type == "string" and test("^[0-9a-fA-F]{40}$"))
' <<<"$base_ref_json")"

statuses="$(gh api "repos/${TARGET_REPOSITORY}/commits/${PR_HEAD_SHA}/statuses")"
pending_matrix='[]'
while IFS= read -r entry; do
Expand Down
29 changes: 28 additions & 1 deletion .github/workflows/codeql-scan-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,14 @@ jobs:
exit 1
fi

# PR base.sha may be a stale snapshot; authenticate the current named branch.
base_ref_path="$(jq -nr --arg ref "$live_base_ref" '$ref | @uri')"
base_ref_json="$(gh api "repos/${TARGET_REPOSITORY}/git/ref/heads/${base_ref_path}")"
live_base_sha="$(jq -er --arg ref "refs/heads/${live_base_ref}" '
select(type == "object" and .ref == $ref and .object.type == "commit")
| .object.sha | select(type == "string" and test("^[0-9a-fA-F]{40}$"))
' <<<"$base_ref_json")"

mismatches=()
[ "$SUPPLIED_BASE_REF" = "$live_base_ref" ] || mismatches+=("base_ref")
[ "$SUPPLIED_BASE_SHA" = "$live_base_sha" ] || mismatches+=("base_sha")
Expand Down Expand Up @@ -536,6 +544,14 @@ jobs:
live_base_sha="$(jq -r '.base.sha // empty' <<<"$pull_request_json")"
live_head_ref="$(jq -r '.head.ref // empty' <<<"$pull_request_json")"
live_head_sha="$(jq -r '.head.sha // empty' <<<"$pull_request_json")"
# PR base.sha may be a stale snapshot; authenticate the current named branch.
base_ref_path="$(jq -nr --arg ref "$live_base_ref" '$ref | @uri')"
base_ref_json="$(gh api "repos/${TARGET_REPOSITORY}/git/ref/heads/${base_ref_path}")"
live_base_sha="$(jq -er --arg ref "refs/heads/${live_base_ref}" '
select(type == "object" and .ref == $ref and .object.type == "commit")
| .object.sha | select(type == "string" and test("^[0-9a-fA-F]{40}$"))
' <<<"$base_ref_json")"

if [ "$live_state" != "open" ] ||
[ "$live_base_ref" != "$EXPECTED_BASE_REF" ] ||
[ "$live_base_sha" != "$EXPECTED_BASE_SHA" ] ||
Expand Down Expand Up @@ -1021,14 +1037,25 @@ jobs:
if [ "$(printf '%s' "$pull" | jq -r '.state // empty')" != "open" ] ||
[ "$(printf '%s' "$pull" | jq -r '.base.repo.full_name // empty')" != "$TARGET_REPOSITORY" ] ||
[ "$(printf '%s' "$pull" | jq -r '.base.ref // empty')" != "$BASE_REF" ] ||
[ "$(printf '%s' "$pull" | jq -r '.base.sha // empty')" != "$BASE_SHA" ] ||
[ "$(printf '%s' "$pull" | jq -r '.head.repo.full_name // empty')" != "$TARGET_REPOSITORY" ] ||
[ "$(printf '%s' "$pull" | jq -r '.head.ref // empty')" != "$HEAD_REF" ] ||
[ "$(printf '%s' "$pull" | jq -r '.head.sha // empty')" != "$HEAD_SHA" ]; then
echo "::error::CodeQL settlement rejected a closed PR, changed base, or stale head."
exit 1
fi

# PR base.sha may be a stale snapshot; authenticate the current named branch.
base_ref_path="$(jq -nr --arg ref "$BASE_REF" '$ref | @uri')"
base_ref_json="$(github_api "repos/${TARGET_REPOSITORY}/git/ref/heads/${base_ref_path}")"
live_base_sha="$(jq -er --arg ref "refs/heads/${BASE_REF}" '
select(type == "object" and .ref == $ref and .object.type == "commit")
| .object.sha | select(type == "string" and test("^[0-9a-fA-F]{40}$"))
' <<<"$base_ref_json")"
if [ "$live_base_sha" != "$BASE_SHA" ]; then
echo "::error::CodeQL settlement rejected a changed base branch."
exit 1
fi

if ! required_run="$(github_api "repos/${TARGET_REPOSITORY}/actions/runs/${REQUIRED_RUN_ID}")"; then
echo "::error::CodeQL settlement could not read the required run."
exit 1
Expand Down
39 changes: 37 additions & 2 deletions tests/test_codeql_pr_workflow_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ def _run_verdict_read(
dispatch_jobs: dict | list[dict] | None = None,
run_attempt: str = "2",
live_state: str = "open",
live_base_snapshot: str = _TEST_BASE_SHA,
base_ref_json: dict | None = None,
live_head: str = _TEST_HEAD_SHA,
comparison: dict | None = None,
required_created_at: str = "2026-09-27T11:08:00Z",
Expand All @@ -277,7 +279,7 @@ def _run_verdict_read(
head_sha = _TEST_HEAD_SHA
live_pr = {
"head": {"sha": live_head},
"base": {"sha": _TEST_BASE_SHA},
"base": {"sha": live_base_snapshot, "ref": "main"},
"merge_commit_sha": _TEST_PRODUCER_SOURCE_SHA,
"state": live_state,
}
Expand All @@ -293,6 +295,7 @@ def _run_verdict_read(
'endpoint="${@: -1}"\n'
'if printf \'%s\\n\' "$@" | grep -qx -- --slurp; then exit 2; fi\n'
'case "$endpoint" in\n'
' */git/ref/heads/*) test "${FAKE_BASE_REF_FAIL:-0}" = 0 || exit 1; if [ -n "${FAKE_BASE_REF_EXPECTED_PATH:-}" ]; then test "${endpoint:-${path:-}}" = "$FAKE_BASE_REF_EXPECTED_PATH"; fi; if [ -n "${FAKE_BASE_REF_JSON:-}" ]; then printf \'%s\\n\' "$FAKE_BASE_REF_JSON"; else printf \'%s\\n\' "$FAKE_PULL_JSON" | jq \'{ref:("refs/heads/"+(.base.ref//"main")),object:{type:"commit",sha:.base.sha}}\'; fi ;;\n'
" */pulls/*) printf '%s\\n' \"$FAKE_PULL_JSON\" ;;\n"
" */compare/*) printf '%s\\n' \"$FAKE_COMPARE_JSON\" ;;\n"
" */statuses) printf '%s\\n' \"$FAKE_STATUSES_JSON\" ;;\n"
Expand All @@ -310,6 +313,7 @@ def _run_verdict_read(
"PATH": f"{fake_bin}:{os.environ['PATH']}",
"FAKE_REQUIRED_CREATED_AT": required_created_at,
"FAKE_PULL_JSON": json.dumps(live_pr),
"FAKE_BASE_REF_JSON": json.dumps(base_ref_json) if base_ref_json is not None else "",
"FAKE_COMPARE_JSON": json.dumps(comparison if comparison is not None else {
"status": "ahead", "behind_by": 0, "ahead_by": 1,
}),
Expand Down Expand Up @@ -766,6 +770,7 @@ def test_codeql_pr_attempt_one_without_verdict_fails_pending_without_dispatch(
'if [[ "$*" == *"/actions/runs/42 --jq .created_at" ]]; then printf \'%s\\n\' "$FAKE_REQUIRED_CREATED_AT"; exit 0; fi\n'
'endpoint="${@: -1}"\n'
'case "$endpoint" in\n'
' */git/ref/heads/*) test "${FAKE_BASE_REF_FAIL:-0}" = 0 || exit 1; if [ -n "${FAKE_BASE_REF_EXPECTED_PATH:-}" ]; then test "${endpoint:-${path:-}}" = "$FAKE_BASE_REF_EXPECTED_PATH"; fi; if [ -n "${FAKE_BASE_REF_JSON:-}" ]; then printf \'%s\\n\' "$FAKE_BASE_REF_JSON"; else printf \'%s\\n\' "$FAKE_PULL_JSON" | jq \'{ref:("refs/heads/"+(.base.ref//"main")),object:{type:"commit",sha:.base.sha}}\'; fi ;;\n'
" */pulls/*) printf '%s\\n' \"$FAKE_PULL_JSON\" ;;\n"
" */statuses) printf '%s\\n' \"$FAKE_STATUSES_JSON\" ;;\n"
" */codeql-scan-dispatch.yml/runs*) printf '%s\\n' \"$FAKE_DISPATCH_RUNS_JSON\" ;;\n"
Expand All @@ -782,7 +787,7 @@ def test_codeql_pr_attempt_one_without_verdict_fails_pending_without_dispatch(
"FAKE_PULL_JSON": json.dumps(
{
"head": {"sha": head_sha},
"base": {"sha": _TEST_BASE_SHA},
"base": {"sha": _TEST_BASE_SHA, "ref": "main"},
"merge_commit_sha": _TEST_PRODUCER_SOURCE_SHA,
"state": "open",
}
Expand Down Expand Up @@ -865,6 +870,7 @@ def _write_coordinator_fakes(
"fi\n"
"body=\n"
'case "$path" in\n'
' */git/ref/heads/*) test "${FAKE_BASE_REF_FAIL:-0}" = 0 || exit 1; if [ -n "${FAKE_BASE_REF_EXPECTED_PATH:-}" ]; then test "${endpoint:-${path:-}}" = "$FAKE_BASE_REF_EXPECTED_PATH"; fi; if [ -n "${FAKE_BASE_REF_JSON:-}" ]; then printf \'%s\\n\' "$FAKE_BASE_REF_JSON"; else printf \'%s\\n\' "$FAKE_PULL_JSON" | jq \'{ref:("refs/heads/"+(.base.ref//"main")),object:{type:"commit",sha:.base.sha}}\'; fi ;;\n'
" */pulls/*) body=$FAKE_PULL_JSON ;;\n"
" */statuses) body=$FAKE_STATUSES_JSON ;;\n"
" */actions/runs/*/jobs) body=$FAKE_JOBS_JSON ;;\n"
Expand Down Expand Up @@ -1203,3 +1209,32 @@ def test_codeql_metadata_jobs_release_runner_on_stalled_api() -> None:
block = re.split(r"\n [a-z][a-z-]*:\n", workflow.split(f"\n {name}:\n", 1)[1], maxsplit=1)[0]
assert re.search(rf"^ timeout-minutes: {minutes}$", block, re.MULTILINE), name
assert "uses: github/codeql-action" not in block


def test_coordinator_uses_branch_tip_when_pr_base_snapshot_is_stale(tmp_path):
"""Dispatch actual named-branch evidence rather than PR creation-time base."""
result, _, body = _run_coordinator(tmp_path, pull={
'state': 'open', 'head': {'sha': 'b' * 40, 'ref': 'feature'},
'base': {'sha': 'f' * 40, 'ref': 'release/2026'}, 'merge_commit_sha': 'c' * 40},
env_overrides={'FAKE_BASE_REF_EXPECTED_PATH': 'repos/ContextualWisdomLab/naruon/git/ref/heads/release%2F2026', 'FAKE_BASE_REF_JSON': json.dumps({
'ref': 'refs/heads/release/2026', 'object': {'type': 'commit', 'sha': 'a' * 40}})})
assert result.returncode == 0, result.stdout + result.stderr
payload = json.loads(body.read_text())['client_payload']
assert payload['pr_base_sha'] == 'a' * 40
assert payload['pr_base_ref'] == 'release/2026'


def test_receipt_reader_uses_current_branch_despite_stale_snapshot(tmp_path):
"""A real bound receipt remains usable when only the PR base snapshot is old."""
read, enforce = _run_verdict_read(tmp_path, [_bound_status('python', 'success')],
live_base_snapshot='f' * 40,
base_ref_json={'ref': 'refs/heads/main',
'object': {'type': 'commit', 'sha': 'a' * 40}})
assert read.returncode == enforce.returncode == 0


def test_coordinator_ref_read_failure_does_not_dispatch(tmp_path):
"""No fallback to historical PR base metadata after an API transport failure."""
result, post, _ = _run_coordinator(tmp_path, env_overrides={'FAKE_BASE_REF_FAIL': '1'})
assert result.returncode != 0
assert not post.exists()
71 changes: 71 additions & 0 deletions tests/test_codeql_scan_dispatch_workflow_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ def _run_validate_step(tmp_path: Path, env_overrides: dict[str, str], pull_reque
'test "$1" = api\n'
'endpoint="${!#}"\n'
'case "$endpoint" in\n'
' */git/ref/heads/*) test "${FAKE_BASE_REF_FAIL:-0}" = 0 || exit 1; if [ -n "${FAKE_BASE_REF_EXPECTED_PATH:-}" ]; then test "${endpoint:-${path:-}}" = "$FAKE_BASE_REF_EXPECTED_PATH"; fi; if [ -n "${FAKE_BASE_REF_JSON:-}" ]; then printf \'%s\\n\' "$FAKE_BASE_REF_JSON"; else printf \'%s\\n\' "$FAKE_PULL_JSON" | jq \'{ref:("refs/heads/"+(.base.ref//"main")),object:{type:"commit",sha:.base.sha}}\'; fi ;;\n'
' repos/ContextualWisdomLab/.github/compare/*) printf \'%s\\n\' "$FAKE_SOURCE_COMPARE_JSON" ;;\n'
' repos/ContextualWisdomLab/*/git/commits/*) printf \'%s\\n\' "$FAKE_PRODUCER_COMMIT_JSON" ;;\n'
' *) printf \'%s\\n\' "$FAKE_PULL_JSON" ;;\n'
Expand Down Expand Up @@ -1212,6 +1213,7 @@ def _run_settlement_step(
" exit 1\n"
"fi\n"
'case "$endpoint" in\n'
' */git/ref/heads/*) test "${FAKE_BASE_REF_FAIL:-0}" = 0 || exit 1; if [ -n "${FAKE_BASE_REF_EXPECTED_PATH:-}" ]; then test "${endpoint:-${path:-}}" = "$FAKE_BASE_REF_EXPECTED_PATH"; fi; if [ -n "${FAKE_BASE_REF_JSON:-}" ]; then printf \'%s\\n\' "$FAKE_BASE_REF_JSON"; else printf \'%s\\n\' "$FAKE_PULL_JSON" | jq \'{ref:("refs/heads/"+(.base.ref//"main")),object:{type:"commit",sha:.base.sha}}\'; fi ;;\n'
' */pulls/*) printf \'%s\\n\' "$FAKE_PULL_JSON" ;;\n'
' repos/ContextualWisdomLab/naruon/actions/runs/42/jobs*) printf \'%s\\n\' "$FAKE_REQUIRED_JOB_PAGES" ;;\n'
' repos/ContextualWisdomLab/naruon/actions/runs/42) printf \'%s\\n\' "$FAKE_RUN_JSON" ;;\n'
Expand Down Expand Up @@ -1819,3 +1821,72 @@ def test_owned_status_configuration_survives_failed_analysis_gate(tmp_path: Path
"GITHUB_OUTPUT": str(output)})
assert result.returncode == 0, result.stdout + result.stderr
assert output.read_text().splitlines() == ["repository=disksage", "available=true"]


def test_stale_pr_base_snapshot_uses_authenticated_branch_ref(tmp_path):
"""A stale PR base snapshot cannot override the actual base branch tip."""
pull = _matching_pull_request()
pull['base']['sha'] = 'f' * 40
result = _run_validate_step(tmp_path, {'FAKE_BASE_REF_JSON': json.dumps({
'ref': 'refs/heads/main', 'object': {'type': 'commit', 'sha': 'a' * 40}})}, pull)
assert result.returncode == 0, result.stdout + result.stderr


@pytest.mark.parametrize('ref', [
{}, {'ref': 'refs/heads/other', 'object': {'type': 'commit', 'sha': 'a' * 40}},
{'ref': 'refs/heads/main', 'object': {'type': 'tag', 'sha': 'a' * 40}},
{'ref': 'refs/heads/main', 'object': {'type': 'commit', 'sha': 'bad'}},
{'ref': 'refs/heads/main', 'object': {'type': 'commit', 'sha': 'f' * 40}},
])
def test_branch_ref_mismatch_cannot_admit_dispatch(tmp_path, ref):
"""Only an exact current branch commit may admit the protected handler."""
result = _run_validate_step(tmp_path, {'FAKE_BASE_REF_JSON': json.dumps(ref)}, _matching_pull_request())
assert result.returncode != 0


def test_settlement_stale_snapshot_does_not_replace_current_branch(tmp_path):
"""Settlement accepts bound current branch despite historical PR base.sha."""
pull = _matching_pull_request()
pull['base']['sha'] = 'f' * 40
result, post = _run_settlement_step(tmp_path, pull=pull, extra_env={
'FAKE_BASE_REF_JSON': json.dumps({'ref': 'refs/heads/main',
'object': {'type': 'commit', 'sha': 'a' * 40}})})
assert result.returncode == 0, result.stdout + result.stderr
assert post.exists()


def test_settlement_changed_branch_does_not_wake_required_run(tmp_path):
"""A moved actual base branch forbids wake even when the PR snapshot matches."""
result, post = _run_settlement_step(tmp_path, extra_env={
'FAKE_BASE_REF_JSON': json.dumps({'ref': 'refs/heads/main',
'object': {'type': 'commit', 'sha': 'f' * 40}})})
assert result.returncode != 0
assert not post.exists()


@pytest.mark.parametrize('branch_sha,expected', [('a' * 40, 0), ('f' * 40, 1)])
def test_prescan_rechecks_actual_base_branch(tmp_path, branch_sha, expected):
"""Execute the privileged boundary with stale PR metadata and live ref drift."""
script = _extract_run_block(WORKFLOW_PATH.read_text(),
'Re-validate live pull request metadata before privileged scan')
fake = tmp_path / 'gh'
fake.write_text('#!/bin/bash\ncase "$*" in\n'
'*git/ref/heads/*) printf "%s\\n" "$FAKE_BASE_REF_JSON" ;;\n'
'*) printf "%s\\n" "$FAKE_PULL_JSON" ;;\nesac\n')
fake.chmod(0o700)
pull = _matching_pull_request()
pull['base']['sha'] = 'e' * 40
env = dict(os.environ, PATH=f"{tmp_path}:{os.environ['PATH']}",
FAKE_PULL_JSON=json.dumps(pull), FAKE_BASE_REF_JSON=json.dumps({
'ref': 'refs/heads/main', 'object': {'type': 'commit', 'sha': branch_sha}}),
TARGET_REPOSITORY='ContextualWisdomLab/naruon', PR_NUMBER='42',
EXPECTED_BASE_REF='main', EXPECTED_BASE_SHA='a' * 40,
EXPECTED_HEAD_REF='feature', EXPECTED_HEAD_SHA='b' * 40)
result = subprocess.run(['/bin/bash', '-c', script], env=env, capture_output=True, text=True)
assert result.returncode == expected, result.stdout + result.stderr


def test_handler_ref_transport_failure_is_closed(tmp_path):
"""No historical-base fallback when authenticated reference fetching fails."""
result = _run_validate_step(tmp_path, {'FAKE_BASE_REF_FAIL': '1'}, _matching_pull_request())
assert result.returncode != 0
Loading