diff --git a/.github/workflows/codeql-scan-dispatch.yml b/.github/workflows/codeql-scan-dispatch.yml index 45cfcc75fc..b0c4f847f9 100644 --- a/.github/workflows/codeql-scan-dispatch.yml +++ b/.github/workflows/codeql-scan-dispatch.yml @@ -263,8 +263,10 @@ jobs: fi if ! [[ "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]] || + [[ "${TARGET_REPOSITORY#ContextualWisdomLab/}" == *".."* ]] || + [[ "${TARGET_REPOSITORY#ContextualWisdomLab/}" == *"." ]] || ! [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]]; then - printf '::error::PR metadata validation rejected a target outside ContextualWisdomLab or an invalid pull request number. target=%s pr=%s\n' "${TARGET_REPOSITORY:-}" "${PR_NUMBER:-}" + printf '::error::PR metadata validation rejected a target outside ContextualWisdomLab or an invalid pull request number. target=%s pr=%s\n' "${TARGET_REPOSITORY:-}" "${PR_NUMBER:-}" >&2 exit 1 fi if [ "$dispatch_protocol" = v2 ] && diff --git a/requirements-strix-ci-hashes.txt b/requirements-strix-ci-hashes.txt index 9e705850b5..eb83beda17 100644 --- a/requirements-strix-ci-hashes.txt +++ b/requirements-strix-ci-hashes.txt @@ -140,9 +140,9 @@ annotated-types==0.7.0 \ --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 # via pydantic -anyio==4.14.0 \ - --hash=sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89 \ - --hash=sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9 +anyio==4.14.2 \ + --hash=sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494 \ + --hash=sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f # via # google-genai # gql diff --git a/tests/test_codeql_scan_dispatch_repository_identity_contract.py b/tests/test_codeql_scan_dispatch_repository_identity_contract.py new file mode 100644 index 0000000000..b755817eb8 --- /dev/null +++ b/tests/test_codeql_scan_dispatch_repository_identity_contract.py @@ -0,0 +1,59 @@ +"""Repository-identity admission contract for the CodeQL dispatch handler.""" + +from __future__ import annotations + +import pytest + +from tests.test_codeql_scan_dispatch_workflow_contract import ( + _matching_pull_request, + _run_validate_step, +) + + +def _matching_pull_request_for(repository: str) -> dict: + """Bind the shared live-PR fixture to one target repository identity.""" + pull_request = _matching_pull_request() + pull_request["base"]["repo"]["full_name"] = repository + pull_request["head"]["repo"]["full_name"] = repository + return pull_request + + +@pytest.mark.parametrize( + "repository", + ( + "ContextualWisdomLab/repository.", + "ContextualWisdomLab/repo..name", + "ContextualWisdomLab/..", + "ContextualWisdomLab/.", + ), +) +def test_codeql_scan_dispatch_rejects_noncanonical_target_repository( + tmp_path, repository: str +) -> None: + """Reject non-canonical target slugs in the real validation shell block.""" + result = _run_validate_step( + tmp_path, + {"TARGET_REPOSITORY": repository}, + _matching_pull_request_for(repository), + ) + assert result.returncode != 0 + assert "PR metadata validation rejected a target outside ContextualWisdomLab" in result.stderr + + +@pytest.mark.parametrize( + "repository", + ( + "ContextualWisdomLab/pg-llm-batch", + "ContextualWisdomLab/repository.name-1", + ), +) +def test_codeql_scan_dispatch_keeps_valid_target_repository( + tmp_path, repository: str +) -> None: + """Preserve valid punctuation-bearing organization-local repository slugs.""" + result = _run_validate_step( + tmp_path, + {"TARGET_REPOSITORY": repository}, + _matching_pull_request_for(repository), + ) + assert result.returncode == 0, result.stderr diff --git a/tests/test_codeql_scan_dispatch_workflow_contract.py b/tests/test_codeql_scan_dispatch_workflow_contract.py index 3769f48314..6c1f91a9d7 100644 --- a/tests/test_codeql_scan_dispatch_workflow_contract.py +++ b/tests/test_codeql_scan_dispatch_workflow_contract.py @@ -724,7 +724,7 @@ def test_codeql_scan_dispatch_validate_step_rejects_non_org_target(tmp_path): ) assert result.returncode == 1 - assert "target outside ContextualWisdomLab" in result.stdout + assert "target outside ContextualWisdomLab" in result.stderr def test_codeql_scan_dispatch_validate_step_rejects_malformed_matrix(tmp_path):