From 62497e56a6063074e47e8ff94bcbb951be030f25 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 08:31:34 +0900 Subject: [PATCH 01/22] fix: keep cross-repo OpenCode evidence healthy --- .../workflows/opencode-review-dispatch.yml | 5 ++- .../materialize_base_python_requirements.py | 15 ++++--- tests/test_opencode_agent_contract.py | 1 + tests/test_trusted_uv_download_contract.py | 45 ++++++++++++------- 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 83f6830d5..4e78d68c2 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -7851,8 +7851,9 @@ jobs: exit 1 fi if [ "${GH_REPOSITORY:-}" != "${GITHUB_REPOSITORY:-}" ] && - [ "${OPENCODE_STATUS_TOKEN_SOURCE:-}" = "github-token" ]; then - echo "::notice::OpenCode repository_dispatch status publication is unavailable because only the same-repository github.token can access cross-repository target ${GH_REPOSITORY}. The exact-head formal review remains authoritative; configure PR_REVIEW_MERGE_TOKEN or OPENCODE_APPROVE_TOKEN to publish the optional commit status." + { [ "${OPENCODE_STATUS_TOKEN_SOURCE:-}" = "github-token" ] || + [ "${OPENCODE_STATUS_TOKEN_SOURCE:-}" = "opencode-app" ]; }; then + echo "::notice::OpenCode repository_dispatch status publication is unavailable because only the same-repository github.token can access cross-repository target ${GH_REPOSITORY}, and the OpenCode App token has no cross-repository commit-status permission in this deployment. The exact-head formal review remains authoritative; configure PR_REVIEW_MERGE_TOKEN or OPENCODE_APPROVE_TOKEN to publish the optional commit status." exit 0 fi diff --git a/scripts/ci/materialize_base_python_requirements.py b/scripts/ci/materialize_base_python_requirements.py index 98cdad459..657e1aa57 100755 --- a/scripts/ci/materialize_base_python_requirements.py +++ b/scripts/ci/materialize_base_python_requirements.py @@ -42,6 +42,7 @@ "https://releases.astral.sh/github/uv/releases/download/0.12.1/" "uv-x86_64-unknown-linux-gnu.tar.gz" ) +TRUSTED_UV_DOWNLOAD_USER_AGENT = "cwl-trusted-uv-materializer/1" TRUSTED_UV_ARCHIVE_SHA256 = ( "90b2f223fb69d19db49e117da601f64978593417988530aa733d456141b4bcbb" ) @@ -169,12 +170,16 @@ def _download_trusted_uv_archive() -> bytes: """Download the fixed uv release archive through one HTTPS trust boundary.""" _install_trusted_uv_url_opener() try: - # Keep the audited URL literal at the network sink so static analysis can - # prove that neither user data nor repository content selects a scheme, - # host, path, query, fragment, method, or request header. - with urllib.request.urlopen( # nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected # nosec B310 + # Keep the audited URL literal and static request header in this trusted + # function so neither user data nor repository content selects the + # scheme, host, path, query, fragment, method, or request header. + request = urllib.request.Request( "https://releases.astral.sh/github/uv/releases/download/0.12.1/" "uv-x86_64-unknown-linux-gnu.tar.gz", + headers={"User-Agent": TRUSTED_UV_DOWNLOAD_USER_AGENT}, + ) + with urllib.request.urlopen( # nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected # nosec B310 + request, timeout=TRUSTED_UV_DOWNLOAD_TIMEOUT_SECONDS, ) as response: final_url = urllib.parse.urlparse(response.geturl()) @@ -532,4 +537,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index daeaa37a2..575285266 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -2019,6 +2019,7 @@ def test_opencode_runs_merge_scheduler_after_review_without_repo_local_dispatch( ) in status_step assert "OPENCODE_STATUS_TOKEN_SOURCE" in status_step assert "steps.opencode_app_token.outputs.available == 'true' && 'opencode-app'" in status_step + assert '[ "${OPENCODE_STATUS_TOKEN_SOURCE:-}" = "opencode-app" ]' in status_step assert "OPENCODE_CHANGED_FILES_FILE" in status_step assert "OPENCODE_ARTIFACT_MANIFEST_SHA256" in status_step assert "OPENCODE_SOURCE_WORKDIR" in status_step diff --git a/tests/test_trusted_uv_download_contract.py b/tests/test_trusted_uv_download_contract.py index 02f3c5961..afc5eae8a 100644 --- a/tests/test_trusted_uv_download_contract.py +++ b/tests/test_trusted_uv_download_contract.py @@ -53,25 +53,16 @@ def _urlopen_calls() -> list[ast.Call]: ] -def test_urlopen_receives_one_literal_https_release_url() -> None: - """Static analysis can prove repository or user data never selects the URL.""" +def test_urlopen_receives_one_static_release_request() -> None: + """Static analysis can prove repository data never selects the request.""" calls = _urlopen_calls() assert len(calls) == 1 assert len(calls[0].args) == 1 - url_argument = calls[0].args[0] - assert isinstance(url_argument, ast.Constant) - assert isinstance(url_argument.value, str) - assert url_argument.value == _EXPECTED_URL - - -def test_literal_network_sink_matches_the_documented_release_constant() -> None: - """The scanner-friendly sink literal cannot drift from the release identity.""" - assert _assigned_literal("TRUSTED_UV_ARCHIVE_URL") == _EXPECTED_URL - + request_argument = calls[0].args[0] + assert isinstance(request_argument, ast.Name) + assert request_argument.id == "request" -def test_downloader_never_constructs_a_dynamic_request_object() -> None: - """The audited downloader cannot hide a dynamic URL inside ``Request``.""" request_calls = [ node for node in ast.walk(_download_function()) @@ -79,8 +70,32 @@ def test_downloader_never_constructs_a_dynamic_request_object() -> None: and isinstance(node.func, ast.Attribute) and node.func.attr == "Request" ] + assert len(request_calls) == 1 + assert len(request_calls[0].args) == 1 + url_argument = request_calls[0].args[0] + assert isinstance(url_argument, ast.Constant) + assert isinstance(url_argument.value, str) + assert url_argument.value == _EXPECTED_URL + + headers = next( + keyword.value + for keyword in request_calls[0].keywords + if keyword.arg == "headers" + ) + assert isinstance(headers, ast.Dict) + assert len(headers.keys) == 1 + assert isinstance(headers.keys[0], ast.Constant) + assert headers.keys[0].value == "User-Agent" + assert isinstance(headers.values[0], ast.Name) + assert headers.values[0].id == "TRUSTED_UV_DOWNLOAD_USER_AGENT" + assert _assigned_literal("TRUSTED_UV_DOWNLOAD_USER_AGENT") == ( + "cwl-trusted-uv-materializer/1" + ) - assert request_calls == [] + +def test_literal_network_sink_matches_the_documented_release_constant() -> None: + """The scanner-friendly sink literal cannot drift from the release identity.""" + assert _assigned_literal("TRUSTED_UV_ARCHIVE_URL") == _EXPECTED_URL def test_literal_urlopen_sink_has_one_scoped_semgrep_suppression() -> None: From c68a7959abc6029c4488179cbbf6e8593892319b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 09:10:03 +0900 Subject: [PATCH 02/22] test(opencode): require formal review before status skip --- tests/test_opencode_agent_contract.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 575285266..85a2c25dd 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -2033,10 +2033,17 @@ def test_opencode_runs_merge_scheduler_after_review_without_repo_local_dispatch( assert "exit 1" in status_step cross_repository_guard = status_step.split( 'if [ "${GH_REPOSITORY:-}" != "${GITHUB_REPOSITORY:-}" ]', 1 - )[1].split("\n fi", 1)[0] + )[1].split("\n\n state=", 1)[0] assert "exact-head formal review remains authoritative" in cross_repository_guard + assert 'formal_review_file="$(mktemp)"' in cross_repository_guard + assert 'gh api "repos/${GH_REPOSITORY}/pulls/${PR_NUMBER}/reviews"' in cross_repository_guard + assert '(.commit_id // "") == $head' in cross_repository_guard + assert 'opencode-agent[bot]' in cross_repository_guard + assert "APPROVED" in cross_repository_guard + assert "CHANGES_REQUESTED" in cross_repository_guard + assert "could not prove an exact-head formal OpenCode review" in cross_repository_guard assert "exit 0" in cross_repository_guard - assert "exit 1" not in cross_repository_guard + assert "exit 1" in cross_repository_guard assert "using %s token" in status_step assert "scripts/ci/opencode_dispatch_status.py" in status_step assert "COVERAGE_EVIDENCE_RESULT" in status_step From f97b9a44201633d2a94bf73c6bb7fde900135553 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 09:10:37 +0900 Subject: [PATCH 03/22] fix(opencode): prove formal review before status skip --- .../workflows/opencode-review-dispatch.yml | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 4e78d68c2..ed97ed75c 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -7853,7 +7853,33 @@ jobs: if [ "${GH_REPOSITORY:-}" != "${GITHUB_REPOSITORY:-}" ] && { [ "${OPENCODE_STATUS_TOKEN_SOURCE:-}" = "github-token" ] || [ "${OPENCODE_STATUS_TOKEN_SOURCE:-}" = "opencode-app" ]; }; then - echo "::notice::OpenCode repository_dispatch status publication is unavailable because only the same-repository github.token can access cross-repository target ${GH_REPOSITORY}, and the OpenCode App token has no cross-repository commit-status permission in this deployment. The exact-head formal review remains authoritative; configure PR_REVIEW_MERGE_TOKEN or OPENCODE_APPROVE_TOKEN to publish the optional commit status." + formal_review_file="$(mktemp)" + cleanup_formal_review_evidence() { + rm -f "$formal_review_file" + } + trap cleanup_formal_review_evidence EXIT + if ! gh api "repos/${GH_REPOSITORY}/pulls/${PR_NUMBER}/reviews" --paginate --slurp | + jq 'flatten' >"$formal_review_file"; then + echo "::error::OpenCode repository_dispatch status publication could not prove an exact-head formal OpenCode review before skipping unavailable cross-repository commit-status publication." + exit 1 + fi + if ! jq -e --arg head "$PR_HEAD_SHA" ' + any(.[]; + (.commit_id // "") == $head + and ( + (.user.login // "") == "opencode-agent[bot]" + or (.user.login // "") == "opencode-agent" + ) + and ( + ((.state // "") | ascii_upcase) == "APPROVED" + or ((.state // "") | ascii_upcase) == "CHANGES_REQUESTED" + ) + ) + ' "$formal_review_file" >/dev/null; then + echo "::error::OpenCode repository_dispatch status publication could not prove an exact-head formal OpenCode review; refusing to hide a cross-repository review-evidence gap." + exit 1 + fi + echo "::notice::OpenCode repository_dispatch status publication is unavailable because only the same-repository github.token can access cross-repository target ${GH_REPOSITORY}, and the OpenCode App token has no cross-repository commit-status permission in this deployment. A verified exact-head formal review remains authoritative; configure PR_REVIEW_MERGE_TOKEN or OPENCODE_APPROVE_TOKEN to publish the optional commit status." exit 0 fi From 4242588b6fae76a3bec38d2cb06680c6ffa78f00 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 09:20:36 +0900 Subject: [PATCH 04/22] fix: fail closed on provider scan evidence --- .github/workflows/strix.yml | 25 ++++----- ...est_strix_nvidia_nim_not_found_fallback.py | 52 ++++++++----------- 2 files changed, 32 insertions(+), 45 deletions(-) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index 03ec23257..44c34a4c4 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -854,28 +854,21 @@ jobs: fi # Preserve configuration failures (exit 2) and any unexpected exit - # code as hard failures — only the scan-failure code (1) can be an - # infrastructure/backend-unavailability outcome. + # code as hard failures. A scan-failure code (1), including provider + # or backend unavailability, is incomplete security evidence and + # remains a hard failure for the required check. if [ "$strix_rc" -ne 1 ]; then exit "$strix_rc" fi # Recognized signals that the LLM backend was unavailable / starved. backend_unavailable_signal='RateLimitError|Too many requests\. For more on scraping GitHub|exceeded your current quota|insufficient_quota|billing details|"status"[[:space:]]*:[[:space:]]*"RESOURCE_EXHAUSTED"|tokens_limit_reached|Request body too large|Max size:[[:space:]]*[0-9]+[[:space:]]+tokens|Error code:[[:space:]]*413|LLM CONNECTION FAILED|Could not establish connection to the language model|LLM warm-up failed|Configured model and fallback models were unavailable|Configured Vertex model and fallback models were unavailable|emitted provider infrastructure or failure-signal output|before provider infrastructure failure|litellm(\.exceptions)?\.NotFoundError[^[:cntrl:]]*Nvidia_nimException[^[:cntrl:]]*Error code:[[:space:]]*404' - # Any evidence that a vulnerability was actually reported. Its presence - # forces a hard failure so real findings are NEVER downgraded. Keep the - # severity branch anchored away from identifiers so environment lines - # such as STRIX_FAIL_ON_MIN_SEVERITY do not look like findings. - reported_vulnerability_signal='Vulnerabilities[[:space:]]+[1-9]|(^|[^A-Za-z0-9_])severity[[:space:]]*:' - - # Neutral skip only when ALL hold: a backend-unavailability signal is - # present and no vulnerability was reported anywhere. This preserves - # real security gating while keeping uncontrollable provider outages - # from blocking current-head merge progress. - if grep -Eiq "$backend_unavailable_signal" "$strix_run_log" \ - && ! grep -Eiq "$reported_vulnerability_signal" "$strix_run_log"; then - echo "::warning title=Strix backend unavailable::Strix could not complete because its LLM backend was unavailable (rate limit / token cap / connection or warm-up failure) before producing a vulnerability report. Treating as a neutral skip so an infrastructure outage does not block merges; genuine findings still fail the check. See the strix-reports artifact and the run log." - exit 0 + # Provider/backend failures are not a clean scan. Keep the required + # check red and let the scheduler or an explicit rerun recover after + # provider capacity returns; never convert missing evidence to a pass. + if grep -Eiq "$backend_unavailable_signal" "$strix_run_log"; then + echo "::error title=Strix backend unavailable::Strix could not complete because its LLM backend was unavailable before producing complete security evidence. The required check remains failed; rerun after provider capacity recovers. See the strix-reports artifact and the run log." + exit "$strix_rc" fi echo "Strix reported security findings or failed for a non-backend reason; failing the required check (gate exit ${strix_rc})." >&2 diff --git a/tests/test_strix_nvidia_nim_not_found_fallback.py b/tests/test_strix_nvidia_nim_not_found_fallback.py index a48f3092d..310acb4a5 100644 --- a/tests/test_strix_nvidia_nim_not_found_fallback.py +++ b/tests/test_strix_nvidia_nim_not_found_fallback.py @@ -85,18 +85,14 @@ def _workflow_signal_pattern(workflow: str, variable_name: str) -> str: return match.group(1) -def _workflow_neutralizes(log_text: str) -> bool: - """Execute the outer workflow's backend-neutralization condition.""" +def _workflow_detects_backend_unavailability(log_text: str) -> bool: + """Execute the outer workflow's backend-unavailability classifier.""" workflow = STRIX_WORKFLOW.read_text(encoding="utf-8") backend_pattern = _workflow_signal_pattern( workflow, "backend_unavailable_signal", ) - vulnerability_pattern = _workflow_signal_pattern( - workflow, - "reported_vulnerability_signal", - ) with tempfile.TemporaryDirectory(prefix="strix-workflow-404-") as temp_dir: log_path = Path(temp_dir) / "strix.log" log_path.write_text(log_text, encoding="utf-8") @@ -106,17 +102,9 @@ def _workflow_neutralizes(log_text: str) -> bool: capture_output=True, text=True, ) - vulnerability = subprocess.run( - ["grep", "-Eiq", vulnerability_pattern, str(log_path)], - check=False, - capture_output=True, - text=True, - ) if backend.returncode not in {0, 1}: raise AssertionError(backend.stderr) - if vulnerability.returncode not in {0, 1}: - raise AssertionError(vulnerability.stderr) - return backend.returncode == 0 and vulnerability.returncode == 1 + return backend.returncode == 0 class StrixNvidiaNotFoundFallbackTests(unittest.TestCase): @@ -200,15 +188,15 @@ def test_workflow_uses_available_free_first_nvidia_plan(self) -> None: self.assertNotIn(RETIRED_PRIMARY_MODEL, default_gate) def test_outer_workflow_requires_litellm_context_for_nvidia_404(self) -> None: - """Reject provider-like target text in the outer neutralization gate.""" + """Reject provider-like target text in the outer failure classifier.""" self.assertFalse( - _workflow_neutralizes( + _workflow_detects_backend_unavailability( "source literal: Nvidia_nimException Error code: 404\n" ) ) self.assertTrue( - _workflow_neutralizes( + _workflow_detects_backend_unavailability( "litellm.exceptions.NotFoundError: Nvidia_nimException - " "Error code: 404\nVulnerabilities 0\n" ) @@ -218,7 +206,7 @@ def test_outer_workflow_rejects_cross_line_signal_assembly(self) -> None: """Require exception, provider, and 404 evidence on one physical line.""" self.assertFalse( - _workflow_neutralizes( + _workflow_detects_backend_unavailability( "litellm.exceptions.NotFoundError: provider unavailable\n" "Nvidia_nimException Error code: 404\n" ) @@ -228,33 +216,39 @@ def test_outer_workflow_rejects_nvidia_404_without_litellm_context(self) -> None """Require LiteLLM NotFoundError context, not just NVIDIA + 404.""" self.assertFalse( - _workflow_neutralizes( + _workflow_detects_backend_unavailability( "Nvidia_nimException Error code: 404\nVulnerabilities 0\n" ) ) - def test_outer_workflow_never_neutralizes_reported_vulnerabilities(self) -> None: - """Keep a real vulnerability signal blocking despite provider failure.""" + def test_outer_workflow_detects_provider_failure_even_with_reported_findings( + self, + ) -> None: + """Keep provider failure evidence visible even beside a finding.""" - self.assertFalse( - _workflow_neutralizes( + self.assertTrue( + _workflow_detects_backend_unavailability( "litellm.exceptions.NotFoundError: Nvidia_nimException - " "Error code: 404\nVulnerabilities 1\n" ) ) - def test_workflow_neutralizes_only_nvidia_404_without_findings(self) -> None: - """Retain the static fail-closed vulnerability evidence contract.""" + def test_workflow_fails_closed_on_provider_unavailability(self) -> None: + """Never turn incomplete provider evidence into a successful check.""" workflow = STRIX_WORKFLOW.read_text(encoding="utf-8") self.assertIn("Nvidia_nimException", workflow) self.assertIn("Error code:[[:space:]]*404", workflow) - self.assertIn("reported_vulnerability_signal", workflow) - self.assertIn("Vulnerabilities[[:space:]]+[1-9]", workflow) self.assertIn( - '! grep -Eiq "$reported_vulnerability_signal"', + 'echo "::error title=Strix backend unavailable::', workflow, ) + self.assertNotIn("reported_vulnerability_signal", workflow) + self.assertNotIn("Treating as a neutral skip", workflow) + failure_block = workflow.split( + "backend_unavailable_signal=", maxsplit=1 + )[1].split("- name: Collect Strix reports", maxsplit=1)[0] + self.assertNotIn("exit 0", failure_block) if __name__ == "__main__": From db88ea6ecf80479465b4b5f349f2a30ac61826d1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 09:24:33 +0900 Subject: [PATCH 05/22] test: align Strix outage contract --- tests/test_required_workflow_queue_contract.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 233c08584..6a0de3820 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -1079,7 +1079,7 @@ def test_optional_strix_workflow_absence_is_logged_without_failing_lookup() -> N assert 'if target_workflow_available "strix.yml"; then' in failed_check_evidence -def test_strix_provider_outage_without_findings_is_neutralized() -> None: +def test_strix_provider_outage_without_findings_fails_closed() -> None: workflow = workflow_text("strix.yml") assert "RateLimitError|Too many requests" in workflow @@ -1087,12 +1087,14 @@ def test_strix_provider_outage_without_findings_is_neutralized() -> None: assert "billing details" in workflow assert "LLM warm-up failed" in workflow assert "zero_vulnerabilities_signal" not in workflow - assert "(^|[^A-Za-z0-9_])severity[[:space:]]*:" in workflow assert "STRIX_FAIL_ON_MIN_SEVERITY: MEDIUM" in workflow - assert "before producing a vulnerability report" in workflow - assert "genuine findings still fail the check" in workflow + assert "before producing complete security evidence" in workflow + assert "The required check remains failed" in workflow + assert "rerun after provider capacity recovers" in workflow + assert "reported_vulnerability_signal" not in workflow assert ( - '&& ! grep -Eiq "$reported_vulnerability_signal" "$strix_run_log"' in workflow + 'if grep -Eiq "$backend_unavailable_signal" "$strix_run_log"; then' + in workflow ) From ac5665148bb113f92e97d2fc49a729bca2f050b5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 09:50:01 +0900 Subject: [PATCH 06/22] docs: align Strix outage gate contract --- .github/workflows/strix.yml | 7 ++++--- tests/test_required_workflow_queue_contract.py | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index 44c34a4c4..c3884b01d 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -839,9 +839,10 @@ jobs: # exit 1 both for genuine blocking vulnerabilities AND for # LLM-backend-unavailable outcomes (GitHub Models "Too many requests" # rate limits, OpenAI quota starvation, 413 tokens_limit_reached - # token-cap, connection/warm-up failures) that could not complete a scan. A backend outage is CI - # infrastructure noise, not a security finding, so it must not fail - # the required check and block merges. + # token-cap, connection/warm-up failures) that could not complete a + # scan. Incomplete provider evidence is not a clean security result, + # so the required check stays failed until a later scheduler pass or + # explicit rerun obtains complete evidence. strix_run_log="$RUNNER_TEMP/strix_gate_console.log" strix_rc=0 set +e diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 6a0de3820..c7a066de3 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -1091,6 +1091,8 @@ def test_strix_provider_outage_without_findings_fails_closed() -> None: assert "before producing complete security evidence" in workflow assert "The required check remains failed" in workflow assert "rerun after provider capacity recovers" in workflow + assert "Incomplete provider evidence is not a clean security result" in workflow + assert "backend outage is CI infrastructure noise" not in workflow assert "reported_vulnerability_signal" not in workflow assert ( 'if grep -Eiq "$backend_unavailable_signal" "$strix_run_log"; then' From ec72661059b94698cf7d28d2108adc156fd7d894 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 12:17:54 +0900 Subject: [PATCH 07/22] docs(review): record cross-repo OpenCode evidence fail-closed Cite RFC 9110 and NIST SP 800-53 for the static User-Agent, exact-head review proof, and Strix incomplete-evidence failure. Isolate Darwin installer tests on the linux x86_64 path. --- CHANGELOG.md | 1 + .../doctoring/cross-repo-opencode-evidence.md | 30 +++++++++++++++++++ ...st_materialize_base_python_requirements.py | 10 +++++++ 3 files changed, 41 insertions(+) create mode 100644 docs/doctoring/cross-repo-opencode-evidence.md diff --git a/CHANGELOG.md b/CHANGELOG.md index bf30091dd..414db8530 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Sent a static User-Agent on the pinned trusted-uv archive request, required an exact-head formal OpenCode review before skipping unavailable cross-repository commit-status publication, and kept Strix failed when provider evidence is incomplete instead of neutralizing an outage into a pass. - Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics. - Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed. - Bound each review-agent invocation key to the wrapper's complete canonical payload, including the base branch and requesting actor; altered fields with a valid-format key now fail before durable-leader election or forwarding, and wrapper write permission is job-scoped. diff --git a/docs/doctoring/cross-repo-opencode-evidence.md b/docs/doctoring/cross-repo-opencode-evidence.md new file mode 100644 index 000000000..24849d7ea --- /dev/null +++ b/docs/doctoring/cross-repo-opencode-evidence.md @@ -0,0 +1,30 @@ +# Cross-repository OpenCode evidence + +## Incident and buyer impact + +Sibling-repo reviews (for example `ContextualWisdomLab/naruon#1317`) lost +coverage-evidence because `releases.astral.sh` rejected the default Python +User-Agent, and the OpenCode App token could not publish a commit status +across repositories. A later Strix provider outage was also converted into +a green required check, so incomplete security evidence looked like a pass. + +## Decision + +1. Send a static `User-Agent: cwl-trusted-uv-materializer/1` on the fixed + Astral HTTPS URL. The URL, no-redirect opener, size bound, checksum, and + executable version checks stay unchanged. +2. Before skipping cross-repository status publication, prove an exact-head + formal OpenCode review (`APPROVED` or `CHANGES_REQUESTED`). Missing proof + fails closed. +3. Keep Strix red when the backend is unavailable. Incomplete provider + evidence is not a clean scan. + +## References + +Fielding, R., Nottingham, M., & Reschke, J. (Eds.). (2022). *HTTP semantics* +(RFC 9110). Internet Engineering Task Force. +https://doi.org/10.17487/RFC9110 + +National Institute of Standards and Technology. (2020). *Security and privacy +controls for information systems and organizations* (NIST Special Publication +800-53 Rev. 5). https://doi.org/10.6028/NIST.SP.800-53r5 diff --git a/tests/test_materialize_base_python_requirements.py b/tests/test_materialize_base_python_requirements.py index 8a383f0c2..10f682b3e 100644 --- a/tests/test_materialize_base_python_requirements.py +++ b/tests/test_materialize_base_python_requirements.py @@ -30,6 +30,13 @@ def _created_tool_directory(path: Path) -> str: return str(path) +def _force_linux_x86_64_installer(monkeypatch: pytest.MonkeyPatch) -> None: + """Exercise the installer path that GitHub-hosted linux x86_64 runners use.""" + monkeypatch.setattr(materializer.sys, "platform", "linux") + monkeypatch.setattr(materializer.platform, "machine", lambda: "x86_64") + materializer._install_trusted_uv.cache_clear() + + def test_materializes_only_regular_hash_locks_from_exact_base(tmp_path: Path) -> None: """A PR-modified lock cannot enter the networked coverage image build context.""" repo = tmp_path / "repo" @@ -644,6 +651,7 @@ def test_install_trusted_uv_verifies_version_and_caches_path( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: """The installer writes one executable, verifies its version, and caches it.""" + _force_linux_x86_64_installer(monkeypatch) tool_dir = tmp_path / "uv" monkeypatch.setattr( materializer.tempfile, @@ -690,6 +698,7 @@ def test_install_trusted_uv_rejects_version_process_failures( failure: OSError | subprocess.TimeoutExpired, ) -> None: """A missing or hung downloaded executable is removed and rejected.""" + _force_linux_x86_64_installer(monkeypatch) tool_dir = tmp_path / "uv" monkeypatch.setattr( materializer.tempfile, @@ -721,6 +730,7 @@ def test_install_trusted_uv_rejects_wrong_version_or_exit_status( completed: subprocess.CompletedProcess[bytes], ) -> None: """Unexpected version output or a nonzero status cannot satisfy the pin.""" + _force_linux_x86_64_installer(monkeypatch) tool_dir = tmp_path / f"uv-{completed.returncode}-{len(completed.stdout)}" monkeypatch.setattr( materializer.tempfile, From 56dbb09ec79e2d4bb2df3ef6e84edb57ecf5783e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 00:44:59 +0900 Subject: [PATCH 08/22] fix(coverage): accept only bounded relative requirement includes Materialize a base Python lock only when every package line is an exact SHA-256 pin or a two-token relative -r/--requirement include of a candidate lock path. A lone --require-hashes directive, ./dotted paths, and -r other-hashes.txt no longer enter the trusted build context. --- AGENTS.md | 2 + CHANGELOG.md | 1 + .../doctoring/cross-repo-opencode-evidence.md | 3 + .../materialize_base_python_requirements.py | 82 +++++++++++++++---- ...st_materialize_base_python_requirements.py | 19 ++++- 5 files changed, 91 insertions(+), 16 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 688b33035..ed8af4167 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,3 +2,5 @@ > **Agents: read the master context FIRST.** Before any work, read [`docs/CWL-MASTER-CONTEXT.md`](docs/CWL-MASTER-CONTEXT.md) (mission · naruon-as-platform + inter-component UML · cross-cutting disciplines · conventions · roadmap · current state), the live **GitHub Project #1** (work/roadmap source of truth), the full spec **ContextualWisdomLab/naruon#974**, and operate the Project per [`docs/agent-github-project-protocol.md`](docs/agent-github-project-protocol.md). The repo/Project — not any private agent memory — is the source of truth. + +Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include (no `.`/`..`); a lone `--require-hashes` directive is not trust evidence. See [`docs/doctoring/cross-repo-opencode-evidence.md`](docs/doctoring/cross-repo-opencode-evidence.md). diff --git a/CHANGELOG.md b/CHANGELOG.md index 414db8530..8851a0f07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context. - Sent a static User-Agent on the pinned trusted-uv archive request, required an exact-head formal OpenCode review before skipping unavailable cross-repository commit-status publication, and kept Strix failed when provider evidence is incomplete instead of neutralizing an outage into a pass. - Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics. - Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed. diff --git a/docs/doctoring/cross-repo-opencode-evidence.md b/docs/doctoring/cross-repo-opencode-evidence.md index 24849d7ea..ff9b7f1d3 100644 --- a/docs/doctoring/cross-repo-opencode-evidence.md +++ b/docs/doctoring/cross-repo-opencode-evidence.md @@ -2,6 +2,9 @@ ## Incident and buyer impact +Materialize accepts only exact SHA-256 pins or a bounded relative `-r` +include; a lone `--require-hashes` line is not lock evidence. + Sibling-repo reviews (for example `ContextualWisdomLab/naruon#1317`) lost coverage-evidence because `releases.astral.sh` rejected the default Python User-Agent, and the OpenCode App token could not publish a commit status diff --git a/scripts/ci/materialize_base_python_requirements.py b/scripts/ci/materialize_base_python_requirements.py index 657e1aa57..311b39e38 100755 --- a/scripts/ci/materialize_base_python_requirements.py +++ b/scripts/ci/materialize_base_python_requirements.py @@ -88,6 +88,57 @@ def _is_candidate_lock_name(name: str) -> bool: ) +def _is_candidate_lock_path(path: pathlib.PurePosixPath) -> bool: + """Return whether one safe tracked path can name a pip requirements lock. + + In addition to conventional ``requirements*.txt`` names, repositories often + keep concrete environment closures as direct children such as + ``requirements/ci.txt`` or ``service/requirements/package.txt``. Only direct + ``.txt`` children of a directory named ``requirements`` gain this path-based + eligibility; content must still pass the independent complete hash-pin + validation before it reaches the trusted image build context. + """ + return _is_candidate_lock_name(path.name) or ( + path.suffix == ".txt" and path.parent.name == "requirements" + ) + + +def _is_bounded_requirement_include(line: str) -> bool: + """Return whether one requirements include names a bounded relative file. + + Includes are accepted only as a two-token ``-r``/``--requirement`` form + whose target is itself a candidate lock path written as a normalized + relative POSIX path. Absolute paths, ``.`` or ``..`` components, double + slashes, URLs, option-like targets, shell/Windows path separators, + fragments, queries, extra inline options or hashes, and includes of + non-lock files are rejected before a base-owned file can enter the + trusted build context. + The downstream installer still proves that the candidate is an independently + complete hash closure; this predicate grants syntax eligibility only. + """ + fields = line.split() + if len(fields) != 2 or fields[0] not in {"-r", "--requirement"}: + return False + target = fields[1] + if ( + target.startswith(("-", "~")) + or "\\" in target + or ":" in target + or "?" in target + or "#" in target + ): + return False + include_path = pathlib.PurePosixPath(target) + return ( + bool(include_path.parts) + and target == include_path.as_posix() + and not include_path.is_absolute() + and "." not in include_path.parts + and ".." not in include_path.parts + and _is_candidate_lock_path(include_path) + ) + + def _requirement_lines(content: bytes) -> list[str]: """Return logical requirement lines, joining backslash line-continuations. @@ -108,23 +159,26 @@ def _requirement_lines(content: bytes) -> list[str]: def _is_hash_pinned(content: bytes) -> bool: - """Return whether content carries hash pins and is safe to preflight. - - Discovery is content-based rather than name-based so hash-pinned locks in any - location (a service subdirectory, ``requirements-dev.txt``, - ``requirements-test.txt``) can be considered for offline coverage, while an - unpinned or PR-mutable requirements file is still excluded from the networked - build context. Hash syntax cannot prove that a file includes every transitive - dependency, so the trusted image installer separately preflights every - candidate as an independent ``--require-hashes`` closure. An empty file - carries no installable dependency and is not materialized. + """Return whether content carries only trusted pins or bounded includes. + + Discovery is content-based rather than name-based so exact hash-pinned locks + in service subdirectories and role-specific requirements files can be + considered for offline coverage. Candidate syntax is deliberately stricter + than a substring search: each package line must be an exact ``==`` pin with + one or more complete SHA-256 hashes, or a bounded relative requirements + include. A global ``--require-hashes`` directive is not trust evidence by + itself. The downstream installer separately preflights every candidate as an + independent ``pip --require-hashes`` closure, so syntax eligibility never + substitutes for dependency-closure proof. """ lines = _requirement_lines(content) - if not lines: + requirement_lines = [line for line in lines if line != "--require-hashes"] + if not requirement_lines: return False - return any(line == "--require-hashes" for line in lines) or all( - "--hash=" in line or line.startswith(("-r ", "--requirement ")) - for line in lines + return all( + _is_fully_hash_pinned_requirement(line) + or _is_bounded_requirement_include(line) + for line in requirement_lines ) diff --git a/tests/test_materialize_base_python_requirements.py b/tests/test_materialize_base_python_requirements.py index 10f682b3e..317ab5f5c 100644 --- a/tests/test_materialize_base_python_requirements.py +++ b/tests/test_materialize_base_python_requirements.py @@ -157,9 +157,24 @@ def test_lock_name_candidates_are_pip_requirements_files() -> None: def test_hash_pin_detection_includes_pinned_and_excludes_unpinned_or_empty() -> None: """Only fully hash-pinned, non-empty lock content is materialized.""" assert not materializer._is_hash_pinned(b"# comment only\n\n") - assert materializer._is_hash_pinned(b"--require-hashes\ndemo==1\n") + assert not materializer._is_hash_pinned(b"--require-hashes\ndemo==1\n") assert materializer._is_hash_pinned(b"demo==1 --hash=sha256:" + b"a" * 64 + b"\n") - assert materializer._is_hash_pinned(b"-r other-hashes.txt\n") + assert materializer._is_hash_pinned(b"-r requirements-other.txt\n") + assert not materializer._is_hash_pinned(b"-r other-hashes.txt\n") + assert not materializer._is_hash_pinned(b"-r ./requirements-other.txt\n") + assert not materializer._is_hash_pinned(b"-r ../escape.txt\n") + assert materializer._is_bounded_requirement_include( + "--requirement requirements-other.txt" + ) + assert not materializer._is_bounded_requirement_include("-r .") + assert not materializer._is_bounded_requirement_include("-r -evil.txt") + assert not materializer._is_bounded_requirement_include("-r ~evil.txt") + assert not materializer._is_bounded_requirement_include("-r C:foo.txt") + assert not materializer._is_bounded_requirement_include("-r foo?bar.txt") + assert not materializer._is_bounded_requirement_include("-r foo#bar.txt") + assert not materializer._is_bounded_requirement_include(r"-r foo\\bar.txt") + assert not materializer._is_bounded_requirement_include("-r") + assert not materializer._is_bounded_requirement_include("-r /abs/requirements.txt") assert not materializer._is_hash_pinned(b"untrusted==1\n") # uv export / pip-compile multi-line continuation format (spec, then --hash= lines). assert materializer._is_hash_pinned( From 8c79abdf1ec322f7e3cb35da62c3042ed7937837 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 17:20:53 +0900 Subject: [PATCH 09/22] ci: repair bounded Strix scope guidance --- .../workflows/repair-pr939-strix-scope.yml | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 .github/workflows/repair-pr939-strix-scope.yml diff --git a/.github/workflows/repair-pr939-strix-scope.yml b/.github/workflows/repair-pr939-strix-scope.yml new file mode 100644 index 000000000..005f45186 --- /dev/null +++ b/.github/workflows/repair-pr939-strix-scope.yml @@ -0,0 +1,141 @@ +name: Repair PR 939 Strix Scope Guidance + +on: + push: + branches: + - codex/fix-cross-repo-opencode-evidence + paths: + - .github/workflows/repair-pr939-strix-scope.yml + +permissions: + contents: write + +concurrency: + group: repair-pr939-strix-scope + cancel-in-progress: false + +jobs: + repair: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Check out the repair branch + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + with: + ref: codex/fix-cross-repo-opencode-evidence + fetch-depth: 0 + + - name: Add bounded-scope guidance and regression contract + shell: bash + run: | + set -euo pipefail + python3 - <<'PY' + from pathlib import Path + + script_path = Path("scripts/ci/strix_quick_gate.sh") + script = script_path.read_text(encoding="utf-8") + + replacements = ( + ( + 'TARGET_PATH_IS_INTERNAL_PR_SCOPE=0\n', + 'TARGET_PATH_IS_INTERNAL_PR_SCOPE=0\n' + 'INTERNAL_PR_SCOPE_INSTRUCTION="This target is a deliberately bounded pull-request changed-file scope mounted by Strix under /workspace/. The original GitHub Actions runner host path is intentionally absent inside the sandbox, and that absence is not a vulnerability. Treat the files in the current working directory as the complete authorized target for this quick changed-path scan. Inspect the available workflow, shell, Python, and configuration files for actionable content vulnerabilities. Do not report the missing host path or intentional scope bounding as a target-code vulnerability."\n', + ), + ( + '\tlocal llm_api_base_value\n\tlocal child_model\n\tlocal resolved_target_path\n', + '\tlocal llm_api_base_value\n\tlocal child_model\n\tlocal child_instruction=""\n\tlocal resolved_target_path\n', + ), + ( + '\tif ! resolved_target_path="$(resolve_current_target_path "$TARGET_PATH")"; then\n\t\treturn 1\n\tfi\n\tlocal start_epoch\n', + '\tif ! resolved_target_path="$(resolve_current_target_path "$TARGET_PATH")"; then\n\t\treturn 1\n\tfi\n\tif [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then\n\t\tchild_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"\n\tfi\n\tlocal start_epoch\n', + ), + ( + '\tSTRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \\\n\tpython3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" <<\'PY\'\n', + '\tSTRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \\\n\tSTRIX_CHILD_INSTRUCTION="$child_instruction" \\\n\tpython3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" <<\'PY\'\n', + ), + ( + 'command = [resolved_strix_bin, "-n", "-t", ".", "--scan-mode", scan_mode]\n\ntry:\n', + 'command = [resolved_strix_bin, "-n", "-t", ".", "--scan-mode", scan_mode]\n' + 'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()\n' + 'if instruction:\n' + ' command.extend(["--instruction", instruction])\n\n' + 'try:\n', + ), + ) + + for old, new in replacements: + count = script.count(old) + if count != 1: + raise SystemExit( + f"expected exactly one patch anchor, found {count}: {old[:100]!r}" + ) + script = script.replace(old, new, 1) + + script_path.write_text(script, encoding="utf-8") + + test_path = Path("tests/test_strix_internal_scope_instruction_contract.py") + test_path.write_text( + '''"""Protect Strix's interpretation of bounded pull-request scan targets."""\n\n' + 'from pathlib import Path\n' + 'import unittest\n\n\n' + 'SCRIPT_PATH = Path("scripts/ci/strix_quick_gate.sh")\n\n\n' + 'class InternalScopeInstructionContractTests(unittest.TestCase):\n' + ' """Keep static sandbox guidance scoped to trusted PR materialization."""\n\n' + ' @classmethod\n' + ' def setUpClass(cls) -> None:\n' + ' """Load the gate implementation once for contract assertions."""\n' + ' cls.script = SCRIPT_PATH.read_text(encoding="utf-8")\n\n' + ' def test_guidance_explains_the_sandbox_mount_contract(self) -> None:\n' + ' """Tell Strix why the runner host path is absent without hiding code."""\n' + ' self.assertIn("deliberately bounded pull-request changed-file scope", self.script)\n' + ' self.assertIn("/workspace/", self.script)\n' + ' self.assertIn("host path is intentionally absent", self.script)\n' + ' self.assertIn("complete authorized target", self.script)\n' + ' self.assertIn("actionable content vulnerabilities", self.script)\n\n' + ' def test_guidance_is_only_selected_for_internal_pr_scope(self) -> None:\n' + ' """Never relay caller-controlled instructions to the security agent."""\n' + ' expected = (\n' + ' \'if [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then\\n\'\n' + ' \'\\t\\tchild_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"\\n\'\n' + ' \'\\tfi\'\n' + ' )\n' + ' self.assertIn(expected, self.script)\n' + ' self.assertIn(\'local child_instruction=""\', self.script)\n' + ' self.assertNotIn(\'STRIX_CHILD_INSTRUCTION="${STRIX_INSTRUCTION\', self.script)\n\n' + ' def test_child_process_receives_the_static_cli_instruction(self) -> None:\n' + ' """Forward the trusted guidance through the stripped child environment."""\n' + ' self.assertIn(\'STRIX_CHILD_INSTRUCTION="$child_instruction"\', self.script)\n' + ' self.assertIn(\n' + ' \'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()\',\n' + ' self.script,\n' + ' )\n' + ' self.assertIn(\'command.extend(["--instruction", instruction])\', self.script)\n\n\n' + 'if __name__ == "__main__":\n' + ' unittest.main()\n''', + encoding="utf-8", + ) + PY + + - name: Verify the Strix gate repair + shell: bash + run: | + set -euo pipefail + bash -n scripts/ci/strix_quick_gate.sh + python3 -m unittest discover \ + --start-directory tests \ + --pattern 'test_strix_internal_scope_instruction_contract.py' \ + --verbose + bash scripts/ci/test_strix_quick_gate.sh + + - name: Commit the verified repair and remove this workflow + shell: bash + run: | + set -euo pipefail + rm -- .github/workflows/repair-pr939-strix-scope.yml + git config user.name "opencode-agent[bot]" + git config user.email "219766164+opencode-agent[bot]@users.noreply.github.com" + git add scripts/ci/strix_quick_gate.sh \ + tests/test_strix_internal_scope_instruction_contract.py \ + .github/workflows/repair-pr939-strix-scope.yml + git commit -m "fix(strix): orient bounded CI scope inside sandbox" + git push origin HEAD:codex/fix-cross-repo-opencode-evidence From 6d916c6abe19b5c0824f3bbdf5576f677d365a60 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 17:30:40 +0900 Subject: [PATCH 10/22] chore(ci): remove failed one-shot repair workflow --- .../workflows/repair-pr939-strix-scope.yml | 141 ------------------ 1 file changed, 141 deletions(-) delete mode 100644 .github/workflows/repair-pr939-strix-scope.yml diff --git a/.github/workflows/repair-pr939-strix-scope.yml b/.github/workflows/repair-pr939-strix-scope.yml deleted file mode 100644 index 005f45186..000000000 --- a/.github/workflows/repair-pr939-strix-scope.yml +++ /dev/null @@ -1,141 +0,0 @@ -name: Repair PR 939 Strix Scope Guidance - -on: - push: - branches: - - codex/fix-cross-repo-opencode-evidence - paths: - - .github/workflows/repair-pr939-strix-scope.yml - -permissions: - contents: write - -concurrency: - group: repair-pr939-strix-scope - cancel-in-progress: false - -jobs: - repair: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - name: Check out the repair branch - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - with: - ref: codex/fix-cross-repo-opencode-evidence - fetch-depth: 0 - - - name: Add bounded-scope guidance and regression contract - shell: bash - run: | - set -euo pipefail - python3 - <<'PY' - from pathlib import Path - - script_path = Path("scripts/ci/strix_quick_gate.sh") - script = script_path.read_text(encoding="utf-8") - - replacements = ( - ( - 'TARGET_PATH_IS_INTERNAL_PR_SCOPE=0\n', - 'TARGET_PATH_IS_INTERNAL_PR_SCOPE=0\n' - 'INTERNAL_PR_SCOPE_INSTRUCTION="This target is a deliberately bounded pull-request changed-file scope mounted by Strix under /workspace/. The original GitHub Actions runner host path is intentionally absent inside the sandbox, and that absence is not a vulnerability. Treat the files in the current working directory as the complete authorized target for this quick changed-path scan. Inspect the available workflow, shell, Python, and configuration files for actionable content vulnerabilities. Do not report the missing host path or intentional scope bounding as a target-code vulnerability."\n', - ), - ( - '\tlocal llm_api_base_value\n\tlocal child_model\n\tlocal resolved_target_path\n', - '\tlocal llm_api_base_value\n\tlocal child_model\n\tlocal child_instruction=""\n\tlocal resolved_target_path\n', - ), - ( - '\tif ! resolved_target_path="$(resolve_current_target_path "$TARGET_PATH")"; then\n\t\treturn 1\n\tfi\n\tlocal start_epoch\n', - '\tif ! resolved_target_path="$(resolve_current_target_path "$TARGET_PATH")"; then\n\t\treturn 1\n\tfi\n\tif [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then\n\t\tchild_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"\n\tfi\n\tlocal start_epoch\n', - ), - ( - '\tSTRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \\\n\tpython3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" <<\'PY\'\n', - '\tSTRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \\\n\tSTRIX_CHILD_INSTRUCTION="$child_instruction" \\\n\tpython3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" <<\'PY\'\n', - ), - ( - 'command = [resolved_strix_bin, "-n", "-t", ".", "--scan-mode", scan_mode]\n\ntry:\n', - 'command = [resolved_strix_bin, "-n", "-t", ".", "--scan-mode", scan_mode]\n' - 'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()\n' - 'if instruction:\n' - ' command.extend(["--instruction", instruction])\n\n' - 'try:\n', - ), - ) - - for old, new in replacements: - count = script.count(old) - if count != 1: - raise SystemExit( - f"expected exactly one patch anchor, found {count}: {old[:100]!r}" - ) - script = script.replace(old, new, 1) - - script_path.write_text(script, encoding="utf-8") - - test_path = Path("tests/test_strix_internal_scope_instruction_contract.py") - test_path.write_text( - '''"""Protect Strix's interpretation of bounded pull-request scan targets."""\n\n' - 'from pathlib import Path\n' - 'import unittest\n\n\n' - 'SCRIPT_PATH = Path("scripts/ci/strix_quick_gate.sh")\n\n\n' - 'class InternalScopeInstructionContractTests(unittest.TestCase):\n' - ' """Keep static sandbox guidance scoped to trusted PR materialization."""\n\n' - ' @classmethod\n' - ' def setUpClass(cls) -> None:\n' - ' """Load the gate implementation once for contract assertions."""\n' - ' cls.script = SCRIPT_PATH.read_text(encoding="utf-8")\n\n' - ' def test_guidance_explains_the_sandbox_mount_contract(self) -> None:\n' - ' """Tell Strix why the runner host path is absent without hiding code."""\n' - ' self.assertIn("deliberately bounded pull-request changed-file scope", self.script)\n' - ' self.assertIn("/workspace/", self.script)\n' - ' self.assertIn("host path is intentionally absent", self.script)\n' - ' self.assertIn("complete authorized target", self.script)\n' - ' self.assertIn("actionable content vulnerabilities", self.script)\n\n' - ' def test_guidance_is_only_selected_for_internal_pr_scope(self) -> None:\n' - ' """Never relay caller-controlled instructions to the security agent."""\n' - ' expected = (\n' - ' \'if [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then\\n\'\n' - ' \'\\t\\tchild_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"\\n\'\n' - ' \'\\tfi\'\n' - ' )\n' - ' self.assertIn(expected, self.script)\n' - ' self.assertIn(\'local child_instruction=""\', self.script)\n' - ' self.assertNotIn(\'STRIX_CHILD_INSTRUCTION="${STRIX_INSTRUCTION\', self.script)\n\n' - ' def test_child_process_receives_the_static_cli_instruction(self) -> None:\n' - ' """Forward the trusted guidance through the stripped child environment."""\n' - ' self.assertIn(\'STRIX_CHILD_INSTRUCTION="$child_instruction"\', self.script)\n' - ' self.assertIn(\n' - ' \'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()\',\n' - ' self.script,\n' - ' )\n' - ' self.assertIn(\'command.extend(["--instruction", instruction])\', self.script)\n\n\n' - 'if __name__ == "__main__":\n' - ' unittest.main()\n''', - encoding="utf-8", - ) - PY - - - name: Verify the Strix gate repair - shell: bash - run: | - set -euo pipefail - bash -n scripts/ci/strix_quick_gate.sh - python3 -m unittest discover \ - --start-directory tests \ - --pattern 'test_strix_internal_scope_instruction_contract.py' \ - --verbose - bash scripts/ci/test_strix_quick_gate.sh - - - name: Commit the verified repair and remove this workflow - shell: bash - run: | - set -euo pipefail - rm -- .github/workflows/repair-pr939-strix-scope.yml - git config user.name "opencode-agent[bot]" - git config user.email "219766164+opencode-agent[bot]@users.noreply.github.com" - git add scripts/ci/strix_quick_gate.sh \ - tests/test_strix_internal_scope_instruction_contract.py \ - .github/workflows/repair-pr939-strix-scope.yml - git commit -m "fix(strix): orient bounded CI scope inside sandbox" - git push origin HEAD:codex/fix-cross-repo-opencode-evidence From b49f36e73cbbe85d3154b381072ab1c064506c3e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 17:30:59 +0900 Subject: [PATCH 11/22] ci: correct Strix scope repair anchors --- .../workflows/repair-pr939-strix-scope.yml | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 .github/workflows/repair-pr939-strix-scope.yml diff --git a/.github/workflows/repair-pr939-strix-scope.yml b/.github/workflows/repair-pr939-strix-scope.yml new file mode 100644 index 000000000..31cfa4058 --- /dev/null +++ b/.github/workflows/repair-pr939-strix-scope.yml @@ -0,0 +1,191 @@ +name: Repair PR 939 Strix Scope Guidance + +on: + push: + branches: + - codex/fix-cross-repo-opencode-evidence + paths: + - .github/workflows/repair-pr939-strix-scope.yml + +permissions: + contents: write + +concurrency: + group: repair-pr939-strix-scope + cancel-in-progress: false + +jobs: + repair: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Check out the repair branch + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + with: + ref: codex/fix-cross-repo-opencode-evidence + fetch-depth: 0 + + - name: Add bounded-scope guidance and regression contract + shell: bash + run: | + set -euo pipefail + python3 - <<'PY' + from pathlib import Path + + script_path = Path("scripts/ci/strix_quick_gate.sh") + script = script_path.read_text(encoding="utf-8") + + replacements = ( + ( + 'LAST_PULL_REQUEST_SCOPE_DIR=""\n' + 'TARGET_PATH_IS_INTERNAL_PR_SCOPE=0\n\n' + 'resolve_trusted_input_file() {', + 'LAST_PULL_REQUEST_SCOPE_DIR=""\n' + 'TARGET_PATH_IS_INTERNAL_PR_SCOPE=0\n' + 'INTERNAL_PR_SCOPE_INSTRUCTION="This target is a deliberately bounded pull-request changed-file scope mounted by Strix under /workspace/. The original GitHub Actions runner host path is intentionally absent inside the sandbox, and that absence is not a vulnerability. Treat the files in the current working directory as the complete authorized target for this quick changed-path scan. Inspect the available workflow, shell, Python, and configuration files for actionable content vulnerabilities. Do not report the missing host path or intentional scope bounding as a target-code vulnerability."\n\n' + 'resolve_trusted_input_file() {', + ), + ( + '\tlocal llm_api_base_value\n' + '\tlocal child_model\n' + '\tlocal resolved_target_path\n', + '\tlocal llm_api_base_value\n' + '\tlocal child_model\n' + '\tlocal child_instruction=""\n' + '\tlocal resolved_target_path\n', + ), + ( + '\tif ! resolved_target_path="$(resolve_current_target_path "$TARGET_PATH")"; then\n' + '\t\treturn 1\n' + '\tfi\n' + '\tlocal start_epoch\n', + '\tif ! resolved_target_path="$(resolve_current_target_path "$TARGET_PATH")"; then\n' + '\t\treturn 1\n' + '\tfi\n' + '\tif [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then\n' + '\t\tchild_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"\n' + '\tfi\n' + '\tlocal start_epoch\n', + ), + ( + '\tSTRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \\\n' + '\tpython3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" <<\'PY\'\n', + '\tSTRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \\\n' + '\tSTRIX_CHILD_INSTRUCTION="$child_instruction" \\\n' + '\tpython3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" <<\'PY\'\n', + ), + ( + 'command = [resolved_strix_bin, "-n", "-t", ".", "--scan-mode", scan_mode]\n\n' + 'try:\n', + 'command = [resolved_strix_bin, "-n", "-t", ".", "--scan-mode", scan_mode]\n' + 'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()\n' + 'if instruction:\n' + ' command.extend(["--instruction", instruction])\n\n' + 'try:\n', + ), + ) + + for old, new in replacements: + count = script.count(old) + if count != 1: + raise SystemExit( + f"expected exactly one patch anchor, found {count}: {old[:120]!r}" + ) + script = script.replace(old, new, 1) + + script_path.write_text(script, encoding="utf-8") + + test_source = '''"""Protect Strix's interpretation of bounded pull-request scan targets.""" + + from pathlib import Path + import unittest + + + SCRIPT_PATH = Path("scripts/ci/strix_quick_gate.sh") + + + class InternalScopeInstructionContractTests(unittest.TestCase): + """Keep static sandbox guidance scoped to trusted PR materialization.""" + + @classmethod + def setUpClass(cls) -> None: + """Load the gate implementation once for contract assertions.""" + cls.script = SCRIPT_PATH.read_text(encoding="utf-8") + + def test_guidance_explains_the_sandbox_mount_contract(self) -> None: + """Tell Strix why the runner host path is absent without hiding code.""" + self.assertIn( + "deliberately bounded pull-request changed-file scope", + self.script, + ) + self.assertIn("/workspace/", self.script) + self.assertIn("host path is intentionally absent", self.script) + self.assertIn("complete authorized target", self.script) + self.assertIn("actionable content vulnerabilities", self.script) + + def test_guidance_is_only_selected_for_internal_pr_scope(self) -> None: + """Never relay caller-controlled instructions to the security agent.""" + expected = ( + 'if [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then\n' + '\t\tchild_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"\n' + '\tfi' + ) + self.assertIn(expected, self.script) + self.assertIn('local child_instruction=""', self.script) + self.assertNotIn( + 'STRIX_CHILD_INSTRUCTION="${STRIX_INSTRUCTION', + self.script, + ) + + def test_child_process_receives_the_static_cli_instruction(self) -> None: + """Forward the trusted guidance through the stripped child environment.""" + self.assertIn( + 'STRIX_CHILD_INSTRUCTION="$child_instruction"', + self.script, + ) + self.assertIn( + 'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()', + self.script, + ) + self.assertIn( + 'command.extend(["--instruction", instruction])', + self.script, + ) + + + if __name__ == "__main__": + unittest.main() + ''' + test_source = "\n".join( + line[10:] if line.startswith(" ") else line + for line in test_source.splitlines() + ) + "\n" + Path("tests/test_strix_internal_scope_instruction_contract.py").write_text( + test_source, + encoding="utf-8", + ) + PY + + - name: Verify the Strix gate repair + shell: bash + run: | + set -euo pipefail + bash -n scripts/ci/strix_quick_gate.sh + python3 -m unittest discover \ + --start-directory tests \ + --pattern 'test_strix_internal_scope_instruction_contract.py' \ + --verbose + bash scripts/ci/test_strix_quick_gate.sh + + - name: Commit the verified repair and remove this workflow + shell: bash + run: | + set -euo pipefail + rm -- .github/workflows/repair-pr939-strix-scope.yml + git config user.name "opencode-agent[bot]" + git config user.email "219766164+opencode-agent[bot]@users.noreply.github.com" + git add scripts/ci/strix_quick_gate.sh \ + tests/test_strix_internal_scope_instruction_contract.py \ + .github/workflows/repair-pr939-strix-scope.yml + git commit -m "fix(strix): orient bounded CI scope inside sandbox" + git push origin HEAD:codex/fix-cross-repo-opencode-evidence From 454ec966adf99bce6b1f4212f718c6db5021b210 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 17:38:30 +0900 Subject: [PATCH 12/22] ci: fix Strix scope repair test source --- .../workflows/repair-pr939-strix-scope.yml | 132 +++++++++--------- 1 file changed, 67 insertions(+), 65 deletions(-) diff --git a/.github/workflows/repair-pr939-strix-scope.yml b/.github/workflows/repair-pr939-strix-scope.yml index 31cfa4058..09d27edf5 100644 --- a/.github/workflows/repair-pr939-strix-scope.yml +++ b/.github/workflows/repair-pr939-strix-scope.yml @@ -31,6 +31,7 @@ jobs: set -euo pipefail python3 - <<'PY' from pathlib import Path + from textwrap import dedent script_path = Path("scripts/ci/strix_quick_gate.sh") script = script_path.read_text(encoding="utf-8") @@ -95,71 +96,72 @@ jobs: script_path.write_text(script, encoding="utf-8") - test_source = '''"""Protect Strix's interpretation of bounded pull-request scan targets.""" - - from pathlib import Path - import unittest - - - SCRIPT_PATH = Path("scripts/ci/strix_quick_gate.sh") - - - class InternalScopeInstructionContractTests(unittest.TestCase): - """Keep static sandbox guidance scoped to trusted PR materialization.""" - - @classmethod - def setUpClass(cls) -> None: - """Load the gate implementation once for contract assertions.""" - cls.script = SCRIPT_PATH.read_text(encoding="utf-8") - - def test_guidance_explains_the_sandbox_mount_contract(self) -> None: - """Tell Strix why the runner host path is absent without hiding code.""" - self.assertIn( - "deliberately bounded pull-request changed-file scope", - self.script, - ) - self.assertIn("/workspace/", self.script) - self.assertIn("host path is intentionally absent", self.script) - self.assertIn("complete authorized target", self.script) - self.assertIn("actionable content vulnerabilities", self.script) - - def test_guidance_is_only_selected_for_internal_pr_scope(self) -> None: - """Never relay caller-controlled instructions to the security agent.""" - expected = ( - 'if [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then\n' - '\t\tchild_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"\n' - '\tfi' - ) - self.assertIn(expected, self.script) - self.assertIn('local child_instruction=""', self.script) - self.assertNotIn( - 'STRIX_CHILD_INSTRUCTION="${STRIX_INSTRUCTION', - self.script, - ) - - def test_child_process_receives_the_static_cli_instruction(self) -> None: - """Forward the trusted guidance through the stripped child environment.""" - self.assertIn( - 'STRIX_CHILD_INSTRUCTION="$child_instruction"', - self.script, - ) - self.assertIn( - 'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()', - self.script, - ) - self.assertIn( - 'command.extend(["--instruction", instruction])', - self.script, - ) - - - if __name__ == "__main__": - unittest.main() - ''' - test_source = "\n".join( - line[10:] if line.startswith(" ") else line - for line in test_source.splitlines() - ) + "\n" + test_source = dedent( + '''\ + """Protect Strix's interpretation of bounded pull-request scan targets.""" + + from pathlib import Path + import unittest + + + SCRIPT_PATH = Path("scripts/ci/strix_quick_gate.sh") + + + class InternalScopeInstructionContractTests(unittest.TestCase): + """Keep static sandbox guidance scoped to trusted PR materialization.""" + + @classmethod + def setUpClass(cls) -> None: + """Load the gate implementation once for contract assertions.""" + cls.script = SCRIPT_PATH.read_text(encoding="utf-8") + + def test_guidance_explains_the_sandbox_mount_contract(self) -> None: + """Tell Strix why the runner host path is absent without hiding code.""" + self.assertIn( + "deliberately bounded pull-request changed-file scope", + self.script, + ) + self.assertIn("/workspace/", self.script) + self.assertIn("host path is intentionally absent", self.script) + self.assertIn("complete authorized target", self.script) + self.assertIn("actionable content vulnerabilities", self.script) + + def test_guidance_is_only_selected_for_internal_pr_scope(self) -> None: + """Never relay caller-controlled instructions to the security agent.""" + self.assertIn( + 'if [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then', + self.script, + ) + self.assertIn( + 'child_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"', + self.script, + ) + self.assertIn('local child_instruction=""', self.script) + self.assertNotIn( + 'STRIX_CHILD_INSTRUCTION="${STRIX_INSTRUCTION', + self.script, + ) + + def test_child_process_receives_the_static_cli_instruction(self) -> None: + """Forward the trusted guidance through the stripped child environment.""" + self.assertIn( + 'STRIX_CHILD_INSTRUCTION="$child_instruction"', + self.script, + ) + self.assertIn( + 'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()', + self.script, + ) + self.assertIn( + 'command.extend(["--instruction", instruction])', + self.script, + ) + + + if __name__ == "__main__": + unittest.main() + ''' + ) Path("tests/test_strix_internal_scope_instruction_contract.py").write_text( test_source, encoding="utf-8", From d012c199ac4ea70c638686fab14553db1d6c738f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 17:39:03 +0900 Subject: [PATCH 13/22] ci: repair Strix scope guidance with literal test fixture --- .../workflows/repair-pr939-strix-scope-v2.yml | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 .github/workflows/repair-pr939-strix-scope-v2.yml diff --git a/.github/workflows/repair-pr939-strix-scope-v2.yml b/.github/workflows/repair-pr939-strix-scope-v2.yml new file mode 100644 index 000000000..dabf1bad6 --- /dev/null +++ b/.github/workflows/repair-pr939-strix-scope-v2.yml @@ -0,0 +1,200 @@ +name: Repair PR 939 Strix Scope Guidance v2 + +on: + push: + branches: + - codex/fix-cross-repo-opencode-evidence + paths: + - .github/workflows/repair-pr939-strix-scope-v2.yml + +permissions: + contents: write + +concurrency: + group: repair-pr939-strix-scope-v2 + cancel-in-progress: false + +jobs: + repair: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Check out the repair branch + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + with: + ref: codex/fix-cross-repo-opencode-evidence + fetch-depth: 0 + + - name: Write the failing regression contract + shell: bash + run: | + set -euo pipefail + cat > tests/test_strix_internal_scope_instruction_contract.py <<'PY' + """Protect Strix's interpretation of bounded pull-request scan targets.""" + + from pathlib import Path + import unittest + + + SCRIPT_PATH = Path("scripts/ci/strix_quick_gate.sh") + + + class InternalScopeInstructionContractTests(unittest.TestCase): + """Keep static sandbox guidance scoped to trusted PR materialization.""" + + @classmethod + def setUpClass(cls) -> None: + """Load the gate implementation once for contract assertions.""" + cls.script = SCRIPT_PATH.read_text(encoding="utf-8") + + def test_guidance_explains_the_sandbox_mount_contract(self) -> None: + """Tell Strix why the runner host path is absent without hiding code.""" + self.assertIn( + "deliberately bounded pull-request changed-file scope", + self.script, + ) + self.assertIn("/workspace/", self.script) + self.assertIn("host path is intentionally absent", self.script) + self.assertIn("complete authorized target", self.script) + self.assertIn("actionable content vulnerabilities", self.script) + + def test_guidance_is_only_selected_for_internal_pr_scope(self) -> None: + """Never relay caller-controlled instructions to the security agent.""" + expected = ( + 'if [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then\n' + '\t\tchild_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"\n' + '\tfi' + ) + self.assertIn(expected, self.script) + self.assertIn('local child_instruction=""', self.script) + self.assertNotIn( + 'STRIX_CHILD_INSTRUCTION="${STRIX_INSTRUCTION', + self.script, + ) + + def test_child_process_receives_the_static_cli_instruction(self) -> None: + """Forward the trusted guidance through the stripped child environment.""" + self.assertIn( + 'STRIX_CHILD_INSTRUCTION="$child_instruction"', + self.script, + ) + self.assertIn( + 'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()', + self.script, + ) + self.assertIn( + 'command.extend(["--instruction", instruction])', + self.script, + ) + + + if __name__ == "__main__": + unittest.main() + PY + if python3 -m unittest discover \ + --start-directory tests \ + --pattern 'test_strix_internal_scope_instruction_contract.py' \ + --verbose; then + echo "ERROR: regression contract unexpectedly passed before implementation." >&2 + exit 1 + fi + + - name: Implement bounded-scope guidance + shell: bash + run: | + set -euo pipefail + python3 - <<'PY' + from pathlib import Path + + script_path = Path("scripts/ci/strix_quick_gate.sh") + script = script_path.read_text(encoding="utf-8") + + replacements = ( + ( + 'LAST_PULL_REQUEST_SCOPE_DIR=""\n' + 'TARGET_PATH_IS_INTERNAL_PR_SCOPE=0\n\n' + 'resolve_trusted_input_file() {', + 'LAST_PULL_REQUEST_SCOPE_DIR=""\n' + 'TARGET_PATH_IS_INTERNAL_PR_SCOPE=0\n' + 'INTERNAL_PR_SCOPE_INSTRUCTION="This target is a deliberately bounded pull-request changed-file scope mounted by Strix under /workspace/. The original GitHub Actions runner host path is intentionally absent inside the sandbox, and that absence is not a vulnerability. Treat the files in the current working directory as the complete authorized target for this quick changed-path scan. Inspect the available workflow, shell, Python, and configuration files for actionable content vulnerabilities. Do not report the missing host path or intentional scope bounding as a target-code vulnerability."\n\n' + 'resolve_trusted_input_file() {', + ), + ( + '\tlocal llm_api_base_value\n' + '\tlocal child_model\n' + '\tlocal resolved_target_path\n', + '\tlocal llm_api_base_value\n' + '\tlocal child_model\n' + '\tlocal child_instruction=""\n' + '\tlocal resolved_target_path\n', + ), + ( + '\tif ! resolved_target_path="$(resolve_current_target_path "$TARGET_PATH")"; then\n' + '\t\treturn 1\n' + '\tfi\n' + '\tlocal start_epoch\n', + '\tif ! resolved_target_path="$(resolve_current_target_path "$TARGET_PATH")"; then\n' + '\t\treturn 1\n' + '\tfi\n' + '\tif [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then\n' + '\t\tchild_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"\n' + '\tfi\n' + '\tlocal start_epoch\n', + ), + ( + '\tSTRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \\\n' + '\tpython3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" <<\'PY\'\n', + '\tSTRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \\\n' + '\tSTRIX_CHILD_INSTRUCTION="$child_instruction" \\\n' + '\tpython3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" <<\'PY\'\n', + ), + ( + 'command = [resolved_strix_bin, "-n", "-t", ".", "--scan-mode", scan_mode]\n\n' + 'try:\n', + 'command = [resolved_strix_bin, "-n", "-t", ".", "--scan-mode", scan_mode]\n' + 'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()\n' + 'if instruction:\n' + ' command.extend(["--instruction", instruction])\n\n' + 'try:\n', + ), + ) + + for old, new in replacements: + count = script.count(old) + if count != 1: + raise SystemExit( + f"expected exactly one patch anchor, found {count}: {old[:120]!r}" + ) + script = script.replace(old, new, 1) + + script_path.write_text(script, encoding="utf-8") + PY + + - name: Verify the Strix gate repair + shell: bash + run: | + set -euo pipefail + bash -n scripts/ci/strix_quick_gate.sh + python3 -m unittest discover \ + --start-directory tests \ + --pattern 'test_strix_internal_scope_instruction_contract.py' \ + --verbose + bash scripts/ci/test_strix_quick_gate.sh + + - name: Commit the verified repair and remove temporary workflows + shell: bash + run: | + set -euo pipefail + git fetch origin codex/fix-cross-repo-opencode-evidence + test "$(git rev-parse HEAD)" = "$(git rev-parse origin/codex/fix-cross-repo-opencode-evidence)" + rm -- \ + .github/workflows/repair-pr939-strix-scope.yml \ + .github/workflows/repair-pr939-strix-scope-v2.yml + git config user.name "opencode-agent[bot]" + git config user.email "219766164+opencode-agent[bot]@users.noreply.github.com" + git add scripts/ci/strix_quick_gate.sh \ + tests/test_strix_internal_scope_instruction_contract.py \ + .github/workflows/repair-pr939-strix-scope.yml \ + .github/workflows/repair-pr939-strix-scope-v2.yml + git commit -m "fix(strix): orient bounded CI scope inside sandbox" + git push origin HEAD:codex/fix-cross-repo-opencode-evidence From 329c49694db5f17a0eaabcdae761252cea3b6e9f Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 08:54:42 +0000 Subject: [PATCH 14/22] fix(strix): orient bounded CI scope inside sandbox --- .../workflows/repair-pr939-strix-scope-v2.yml | 200 ------------------ .../workflows/repair-pr939-strix-scope.yml | 193 ----------------- scripts/ci/strix_quick_gate.sh | 9 + ...rix_internal_scope_instruction_contract.py | 60 ++++++ 4 files changed, 69 insertions(+), 393 deletions(-) delete mode 100644 .github/workflows/repair-pr939-strix-scope-v2.yml delete mode 100644 .github/workflows/repair-pr939-strix-scope.yml create mode 100644 tests/test_strix_internal_scope_instruction_contract.py diff --git a/.github/workflows/repair-pr939-strix-scope-v2.yml b/.github/workflows/repair-pr939-strix-scope-v2.yml deleted file mode 100644 index dabf1bad6..000000000 --- a/.github/workflows/repair-pr939-strix-scope-v2.yml +++ /dev/null @@ -1,200 +0,0 @@ -name: Repair PR 939 Strix Scope Guidance v2 - -on: - push: - branches: - - codex/fix-cross-repo-opencode-evidence - paths: - - .github/workflows/repair-pr939-strix-scope-v2.yml - -permissions: - contents: write - -concurrency: - group: repair-pr939-strix-scope-v2 - cancel-in-progress: false - -jobs: - repair: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - name: Check out the repair branch - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - with: - ref: codex/fix-cross-repo-opencode-evidence - fetch-depth: 0 - - - name: Write the failing regression contract - shell: bash - run: | - set -euo pipefail - cat > tests/test_strix_internal_scope_instruction_contract.py <<'PY' - """Protect Strix's interpretation of bounded pull-request scan targets.""" - - from pathlib import Path - import unittest - - - SCRIPT_PATH = Path("scripts/ci/strix_quick_gate.sh") - - - class InternalScopeInstructionContractTests(unittest.TestCase): - """Keep static sandbox guidance scoped to trusted PR materialization.""" - - @classmethod - def setUpClass(cls) -> None: - """Load the gate implementation once for contract assertions.""" - cls.script = SCRIPT_PATH.read_text(encoding="utf-8") - - def test_guidance_explains_the_sandbox_mount_contract(self) -> None: - """Tell Strix why the runner host path is absent without hiding code.""" - self.assertIn( - "deliberately bounded pull-request changed-file scope", - self.script, - ) - self.assertIn("/workspace/", self.script) - self.assertIn("host path is intentionally absent", self.script) - self.assertIn("complete authorized target", self.script) - self.assertIn("actionable content vulnerabilities", self.script) - - def test_guidance_is_only_selected_for_internal_pr_scope(self) -> None: - """Never relay caller-controlled instructions to the security agent.""" - expected = ( - 'if [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then\n' - '\t\tchild_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"\n' - '\tfi' - ) - self.assertIn(expected, self.script) - self.assertIn('local child_instruction=""', self.script) - self.assertNotIn( - 'STRIX_CHILD_INSTRUCTION="${STRIX_INSTRUCTION', - self.script, - ) - - def test_child_process_receives_the_static_cli_instruction(self) -> None: - """Forward the trusted guidance through the stripped child environment.""" - self.assertIn( - 'STRIX_CHILD_INSTRUCTION="$child_instruction"', - self.script, - ) - self.assertIn( - 'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()', - self.script, - ) - self.assertIn( - 'command.extend(["--instruction", instruction])', - self.script, - ) - - - if __name__ == "__main__": - unittest.main() - PY - if python3 -m unittest discover \ - --start-directory tests \ - --pattern 'test_strix_internal_scope_instruction_contract.py' \ - --verbose; then - echo "ERROR: regression contract unexpectedly passed before implementation." >&2 - exit 1 - fi - - - name: Implement bounded-scope guidance - shell: bash - run: | - set -euo pipefail - python3 - <<'PY' - from pathlib import Path - - script_path = Path("scripts/ci/strix_quick_gate.sh") - script = script_path.read_text(encoding="utf-8") - - replacements = ( - ( - 'LAST_PULL_REQUEST_SCOPE_DIR=""\n' - 'TARGET_PATH_IS_INTERNAL_PR_SCOPE=0\n\n' - 'resolve_trusted_input_file() {', - 'LAST_PULL_REQUEST_SCOPE_DIR=""\n' - 'TARGET_PATH_IS_INTERNAL_PR_SCOPE=0\n' - 'INTERNAL_PR_SCOPE_INSTRUCTION="This target is a deliberately bounded pull-request changed-file scope mounted by Strix under /workspace/. The original GitHub Actions runner host path is intentionally absent inside the sandbox, and that absence is not a vulnerability. Treat the files in the current working directory as the complete authorized target for this quick changed-path scan. Inspect the available workflow, shell, Python, and configuration files for actionable content vulnerabilities. Do not report the missing host path or intentional scope bounding as a target-code vulnerability."\n\n' - 'resolve_trusted_input_file() {', - ), - ( - '\tlocal llm_api_base_value\n' - '\tlocal child_model\n' - '\tlocal resolved_target_path\n', - '\tlocal llm_api_base_value\n' - '\tlocal child_model\n' - '\tlocal child_instruction=""\n' - '\tlocal resolved_target_path\n', - ), - ( - '\tif ! resolved_target_path="$(resolve_current_target_path "$TARGET_PATH")"; then\n' - '\t\treturn 1\n' - '\tfi\n' - '\tlocal start_epoch\n', - '\tif ! resolved_target_path="$(resolve_current_target_path "$TARGET_PATH")"; then\n' - '\t\treturn 1\n' - '\tfi\n' - '\tif [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then\n' - '\t\tchild_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"\n' - '\tfi\n' - '\tlocal start_epoch\n', - ), - ( - '\tSTRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \\\n' - '\tpython3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" <<\'PY\'\n', - '\tSTRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \\\n' - '\tSTRIX_CHILD_INSTRUCTION="$child_instruction" \\\n' - '\tpython3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" <<\'PY\'\n', - ), - ( - 'command = [resolved_strix_bin, "-n", "-t", ".", "--scan-mode", scan_mode]\n\n' - 'try:\n', - 'command = [resolved_strix_bin, "-n", "-t", ".", "--scan-mode", scan_mode]\n' - 'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()\n' - 'if instruction:\n' - ' command.extend(["--instruction", instruction])\n\n' - 'try:\n', - ), - ) - - for old, new in replacements: - count = script.count(old) - if count != 1: - raise SystemExit( - f"expected exactly one patch anchor, found {count}: {old[:120]!r}" - ) - script = script.replace(old, new, 1) - - script_path.write_text(script, encoding="utf-8") - PY - - - name: Verify the Strix gate repair - shell: bash - run: | - set -euo pipefail - bash -n scripts/ci/strix_quick_gate.sh - python3 -m unittest discover \ - --start-directory tests \ - --pattern 'test_strix_internal_scope_instruction_contract.py' \ - --verbose - bash scripts/ci/test_strix_quick_gate.sh - - - name: Commit the verified repair and remove temporary workflows - shell: bash - run: | - set -euo pipefail - git fetch origin codex/fix-cross-repo-opencode-evidence - test "$(git rev-parse HEAD)" = "$(git rev-parse origin/codex/fix-cross-repo-opencode-evidence)" - rm -- \ - .github/workflows/repair-pr939-strix-scope.yml \ - .github/workflows/repair-pr939-strix-scope-v2.yml - git config user.name "opencode-agent[bot]" - git config user.email "219766164+opencode-agent[bot]@users.noreply.github.com" - git add scripts/ci/strix_quick_gate.sh \ - tests/test_strix_internal_scope_instruction_contract.py \ - .github/workflows/repair-pr939-strix-scope.yml \ - .github/workflows/repair-pr939-strix-scope-v2.yml - git commit -m "fix(strix): orient bounded CI scope inside sandbox" - git push origin HEAD:codex/fix-cross-repo-opencode-evidence diff --git a/.github/workflows/repair-pr939-strix-scope.yml b/.github/workflows/repair-pr939-strix-scope.yml deleted file mode 100644 index 09d27edf5..000000000 --- a/.github/workflows/repair-pr939-strix-scope.yml +++ /dev/null @@ -1,193 +0,0 @@ -name: Repair PR 939 Strix Scope Guidance - -on: - push: - branches: - - codex/fix-cross-repo-opencode-evidence - paths: - - .github/workflows/repair-pr939-strix-scope.yml - -permissions: - contents: write - -concurrency: - group: repair-pr939-strix-scope - cancel-in-progress: false - -jobs: - repair: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - name: Check out the repair branch - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - with: - ref: codex/fix-cross-repo-opencode-evidence - fetch-depth: 0 - - - name: Add bounded-scope guidance and regression contract - shell: bash - run: | - set -euo pipefail - python3 - <<'PY' - from pathlib import Path - from textwrap import dedent - - script_path = Path("scripts/ci/strix_quick_gate.sh") - script = script_path.read_text(encoding="utf-8") - - replacements = ( - ( - 'LAST_PULL_REQUEST_SCOPE_DIR=""\n' - 'TARGET_PATH_IS_INTERNAL_PR_SCOPE=0\n\n' - 'resolve_trusted_input_file() {', - 'LAST_PULL_REQUEST_SCOPE_DIR=""\n' - 'TARGET_PATH_IS_INTERNAL_PR_SCOPE=0\n' - 'INTERNAL_PR_SCOPE_INSTRUCTION="This target is a deliberately bounded pull-request changed-file scope mounted by Strix under /workspace/. The original GitHub Actions runner host path is intentionally absent inside the sandbox, and that absence is not a vulnerability. Treat the files in the current working directory as the complete authorized target for this quick changed-path scan. Inspect the available workflow, shell, Python, and configuration files for actionable content vulnerabilities. Do not report the missing host path or intentional scope bounding as a target-code vulnerability."\n\n' - 'resolve_trusted_input_file() {', - ), - ( - '\tlocal llm_api_base_value\n' - '\tlocal child_model\n' - '\tlocal resolved_target_path\n', - '\tlocal llm_api_base_value\n' - '\tlocal child_model\n' - '\tlocal child_instruction=""\n' - '\tlocal resolved_target_path\n', - ), - ( - '\tif ! resolved_target_path="$(resolve_current_target_path "$TARGET_PATH")"; then\n' - '\t\treturn 1\n' - '\tfi\n' - '\tlocal start_epoch\n', - '\tif ! resolved_target_path="$(resolve_current_target_path "$TARGET_PATH")"; then\n' - '\t\treturn 1\n' - '\tfi\n' - '\tif [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then\n' - '\t\tchild_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"\n' - '\tfi\n' - '\tlocal start_epoch\n', - ), - ( - '\tSTRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \\\n' - '\tpython3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" <<\'PY\'\n', - '\tSTRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \\\n' - '\tSTRIX_CHILD_INSTRUCTION="$child_instruction" \\\n' - '\tpython3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" <<\'PY\'\n', - ), - ( - 'command = [resolved_strix_bin, "-n", "-t", ".", "--scan-mode", scan_mode]\n\n' - 'try:\n', - 'command = [resolved_strix_bin, "-n", "-t", ".", "--scan-mode", scan_mode]\n' - 'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()\n' - 'if instruction:\n' - ' command.extend(["--instruction", instruction])\n\n' - 'try:\n', - ), - ) - - for old, new in replacements: - count = script.count(old) - if count != 1: - raise SystemExit( - f"expected exactly one patch anchor, found {count}: {old[:120]!r}" - ) - script = script.replace(old, new, 1) - - script_path.write_text(script, encoding="utf-8") - - test_source = dedent( - '''\ - """Protect Strix's interpretation of bounded pull-request scan targets.""" - - from pathlib import Path - import unittest - - - SCRIPT_PATH = Path("scripts/ci/strix_quick_gate.sh") - - - class InternalScopeInstructionContractTests(unittest.TestCase): - """Keep static sandbox guidance scoped to trusted PR materialization.""" - - @classmethod - def setUpClass(cls) -> None: - """Load the gate implementation once for contract assertions.""" - cls.script = SCRIPT_PATH.read_text(encoding="utf-8") - - def test_guidance_explains_the_sandbox_mount_contract(self) -> None: - """Tell Strix why the runner host path is absent without hiding code.""" - self.assertIn( - "deliberately bounded pull-request changed-file scope", - self.script, - ) - self.assertIn("/workspace/", self.script) - self.assertIn("host path is intentionally absent", self.script) - self.assertIn("complete authorized target", self.script) - self.assertIn("actionable content vulnerabilities", self.script) - - def test_guidance_is_only_selected_for_internal_pr_scope(self) -> None: - """Never relay caller-controlled instructions to the security agent.""" - self.assertIn( - 'if [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then', - self.script, - ) - self.assertIn( - 'child_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"', - self.script, - ) - self.assertIn('local child_instruction=""', self.script) - self.assertNotIn( - 'STRIX_CHILD_INSTRUCTION="${STRIX_INSTRUCTION', - self.script, - ) - - def test_child_process_receives_the_static_cli_instruction(self) -> None: - """Forward the trusted guidance through the stripped child environment.""" - self.assertIn( - 'STRIX_CHILD_INSTRUCTION="$child_instruction"', - self.script, - ) - self.assertIn( - 'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()', - self.script, - ) - self.assertIn( - 'command.extend(["--instruction", instruction])', - self.script, - ) - - - if __name__ == "__main__": - unittest.main() - ''' - ) - Path("tests/test_strix_internal_scope_instruction_contract.py").write_text( - test_source, - encoding="utf-8", - ) - PY - - - name: Verify the Strix gate repair - shell: bash - run: | - set -euo pipefail - bash -n scripts/ci/strix_quick_gate.sh - python3 -m unittest discover \ - --start-directory tests \ - --pattern 'test_strix_internal_scope_instruction_contract.py' \ - --verbose - bash scripts/ci/test_strix_quick_gate.sh - - - name: Commit the verified repair and remove this workflow - shell: bash - run: | - set -euo pipefail - rm -- .github/workflows/repair-pr939-strix-scope.yml - git config user.name "opencode-agent[bot]" - git config user.email "219766164+opencode-agent[bot]@users.noreply.github.com" - git add scripts/ci/strix_quick_gate.sh \ - tests/test_strix_internal_scope_instruction_contract.py \ - .github/workflows/repair-pr939-strix-scope.yml - git commit -m "fix(strix): orient bounded CI scope inside sandbox" - git push origin HEAD:codex/fix-cross-repo-opencode-evidence diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 0f37f3460..cde2472f0 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -68,6 +68,7 @@ NORMALIZED_CHANGED_FILES=() PULL_REQUEST_SCOPE_DIRS=() LAST_PULL_REQUEST_SCOPE_DIR="" TARGET_PATH_IS_INTERNAL_PR_SCOPE=0 +INTERNAL_PR_SCOPE_INSTRUCTION="This target is a deliberately bounded pull-request changed-file scope mounted by Strix under /workspace/. The original GitHub Actions runner host path is intentionally absent inside the sandbox, and that absence is not a vulnerability. Treat the files in the current working directory as the complete authorized target for this quick changed-path scan. Inspect the available workflow, shell, Python, and configuration files for actionable content vulnerabilities. Do not report the missing host path or intentional scope bounding as a target-code vulnerability." resolve_trusted_input_file() { local label="$1" @@ -2328,6 +2329,7 @@ run_strix_once() { local rc local llm_api_base_value local child_model + local child_instruction="" local resolved_target_path local timeout_seconds="$STRIX_PROCESS_TIMEOUT_SECONDS" local total_budget_limited_timeout=0 @@ -2354,6 +2356,9 @@ run_strix_once() { if ! resolved_target_path="$(resolve_current_target_path "$TARGET_PATH")"; then return 1 fi + if [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then + child_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION" + fi local start_epoch start_epoch="$(date +%s)" local child_llm_api_key="" @@ -2375,6 +2380,7 @@ run_strix_once() { STRIX_CHILD_EXECUTABLE_ROOT="$STRIX_EXECUTABLE_ROOT" \ STRIX_CHILD_EXECUTABLE_SHA256="$STRIX_EXECUTABLE_SHA256" \ STRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \ + STRIX_CHILD_INSTRUCTION="$child_instruction" \ python3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" <<'PY' import hashlib import hmac @@ -2529,6 +2535,9 @@ if any(ch in str(target_cwd) for ch in ("\x00", "\n", "\r")): raise SystemExit(2) command = [resolved_strix_bin, "-n", "-t", ".", "--scan-mode", scan_mode] +instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip() +if instruction: + command.extend(["--instruction", instruction]) try: process = subprocess.Popen( diff --git a/tests/test_strix_internal_scope_instruction_contract.py b/tests/test_strix_internal_scope_instruction_contract.py new file mode 100644 index 000000000..84c1dac99 --- /dev/null +++ b/tests/test_strix_internal_scope_instruction_contract.py @@ -0,0 +1,60 @@ +"""Protect Strix's interpretation of bounded pull-request scan targets.""" + +from pathlib import Path +import unittest + + +SCRIPT_PATH = Path("scripts/ci/strix_quick_gate.sh") + + +class InternalScopeInstructionContractTests(unittest.TestCase): + """Keep static sandbox guidance scoped to trusted PR materialization.""" + + @classmethod + def setUpClass(cls) -> None: + """Load the gate implementation once for contract assertions.""" + cls.script = SCRIPT_PATH.read_text(encoding="utf-8") + + def test_guidance_explains_the_sandbox_mount_contract(self) -> None: + """Tell Strix why the runner host path is absent without hiding code.""" + self.assertIn( + "deliberately bounded pull-request changed-file scope", + self.script, + ) + self.assertIn("/workspace/", self.script) + self.assertIn("host path is intentionally absent", self.script) + self.assertIn("complete authorized target", self.script) + self.assertIn("actionable content vulnerabilities", self.script) + + def test_guidance_is_only_selected_for_internal_pr_scope(self) -> None: + """Never relay caller-controlled instructions to the security agent.""" + expected = ( + 'if [ "$TARGET_PATH_IS_INTERNAL_PR_SCOPE" -eq 1 ]; then\n' + '\t\tchild_instruction="$INTERNAL_PR_SCOPE_INSTRUCTION"\n' + '\tfi' + ) + self.assertIn(expected, self.script) + self.assertIn('local child_instruction=""', self.script) + self.assertNotIn( + 'STRIX_CHILD_INSTRUCTION="${STRIX_INSTRUCTION', + self.script, + ) + + def test_child_process_receives_the_static_cli_instruction(self) -> None: + """Forward the trusted guidance through the stripped child environment.""" + self.assertIn( + 'STRIX_CHILD_INSTRUCTION="$child_instruction"', + self.script, + ) + self.assertIn( + 'instruction = os.environ.get("STRIX_CHILD_INSTRUCTION", "").strip()', + self.script, + ) + self.assertIn( + 'command.extend(["--instruction", instruction])', + self.script, + ) + + +if __name__ == "__main__": + unittest.main() From a0b48d2d7e1587715e218257330822bce2fe43a1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 17:57:19 +0900 Subject: [PATCH 15/22] docs(strix): record bounded PR scope and CI recursion contract --- docs/doctoring/strix-bounded-pr-scope.md | 62 ++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 docs/doctoring/strix-bounded-pr-scope.md diff --git a/docs/doctoring/strix-bounded-pr-scope.md b/docs/doctoring/strix-bounded-pr-scope.md new file mode 100644 index 000000000..6b2755879 --- /dev/null +++ b/docs/doctoring/strix-bounded-pr-scope.md @@ -0,0 +1,62 @@ +# Strix bounded pull-request scope and CI recursion contract + +## Purpose + +This doctoring record defines the trusted boundary used when Strix reviews a bounded set of pull-request changes from an organization-required workflow. It also records the GitHub Actions recursion behavior encountered while repairing the boundary so that future maintainers do not misclassify infrastructure state as a target-code defect. + +## Incident chain + +A downstream OpenCode review dispatch for `ContextualWisdomLab/pg-llm-batch#190` failed while the central workflow materialized its trusted `uv` executable. The trusted download retained a fixed Astral release URL, a no-proxy/no-redirect opener, a bounded response read, SHA-256 verification, and executable-version verification, but the request did not identify the organization client. Pull request #939 adds a fixed `User-Agent` and regression coverage without weakening those trust checks. + +During verification of the central repair, Strix received an intentionally bounded pull-request target. The GitHub Actions runner created that target below a host temporary directory, while the Strix sandbox mounted the same files below `/workspace/`. The original host path was intentionally absent inside the sandbox. Treating that absence as a missing-code vulnerability was therefore a scanner-orientation error, not a finding in the pull-request content. + +Repair workflow run `31784776654` established the regression test first, applied the trusted static scope guidance, ran shell syntax validation, ran the focused Python contract, and completed the full `scripts/ci/test_strix_quick_gate.sh` harness before committing the production change. Both temporary repair workflows were removed by the verified commit. + +## Trusted scope contract + +The following invariants apply: + +1. `pull_request_target` executes the protected-base workflow and trusted gate implementation. Pull-request content is materialized as data in a separate bounded directory; it is not executed with privileged credentials. +2. A target created under the runner host temporary directory may be mounted at `/workspace/` inside the Strix sandbox. Absence of the original host pathname inside the sandbox is expected. +3. For the internal bounded pull-request scope only, the trusted gate supplies a static instruction explaining the mount contract and directing Strix to inspect the files present in the current working directory. +4. No repository input, dispatch payload, pull-request field, environment override, or caller-supplied instruction is forwarded to the security model. The instruction is selected only when `TARGET_PATH_IS_INTERNAL_PR_SCOPE=1` was set by trusted scope materialization. +5. The bounded directory is the complete authorized target for the changed-path scan. Strix must continue to report actionable vulnerabilities in the workflow, shell, Python, configuration, and other eligible files that are actually present. +6. Scope orientation must not suppress provider failures, malformed reports, integrity failures, missing authorized files, or vulnerabilities in present content. Those conditions remain fail-closed. + +## GitHub Actions recursion behavior + +The verified repair commit was pushed by a workflow using the repository `GITHUB_TOKEN`. GitHub created the resulting pull-request workflow runs in an approval-required state and reported `action_required` without jobs. This is GitHub's recursion protection rather than test execution evidence. A maintainer-authenticated commit or explicit workflow approval is required before exact-head CI can run normally. + +This repository must not replace the recursion protection with a broadly privileged token merely to make a self-repair workflow recursively trigger CI. Temporary repair workflows must remain narrowly scoped, use least-privilege `contents: write`, verify that the remote branch has not advanced, run the full regression harness before pushing, and delete themselves from the resulting production commit. + +## Regression evidence + +The minimum local or CI evidence for this boundary is: + +```bash +bash -n scripts/ci/strix_quick_gate.sh +python3 -m unittest discover \ + --start-directory tests \ + --pattern 'test_strix_internal_scope_instruction_contract.py' \ + --verbose +bash scripts/ci/test_strix_quick_gate.sh +``` + +The exact pull-request head must additionally complete the trusted-uv materializer quality workflow, Strix changed-path quality workflow, repository security workflows, required OpenCode review, required Strix scan, and all protected-branch review requirements. A previous-head repair run, an approval-required run with no jobs, or a downstream repository's successful leaf checks cannot substitute for current-head central evidence. + +## Operational recovery sequence + +1. Confirm the downstream source head and reproduce the central failure against that exact SHA. +2. Repair the central trusted implementation; do not add unrelated downstream source changes. +3. Add a regression contract that fails before the central repair and passes after it. +4. Verify trusted URL, redirect, proxy, size, checksum, executable-version, and credential boundaries remain intact. +5. Run the full Strix gate harness before committing a scope-orientation change. +6. Remove temporary repair automation from the production diff. +7. Obtain exact-head central CI and independent approvals without dismissing reviews or bypassing branch protection. +8. Merge the central repair normally, then rerun the downstream review on the unchanged downstream head so the infrastructure-derived review is superseded through the standard review path. + +## References + +GitHub. (n.d.). *GITHUB_TOKEN*. GitHub Docs. Retrieved August 14, 2026, from https://docs.github.com/en/actions/concepts/security/github_token + +GitHub. (n.d.). *Securely using pull_request_target*. GitHub Docs. Retrieved August 14, 2026, from https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target From 2fc94f69f090b39bd880f849010e909c6c6e98cf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 18:05:44 +0900 Subject: [PATCH 16/22] ci: repair nested requirements lock discovery --- .github/workflows/repair-pr939-lock-path.yml | 139 +++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 .github/workflows/repair-pr939-lock-path.yml diff --git a/.github/workflows/repair-pr939-lock-path.yml b/.github/workflows/repair-pr939-lock-path.yml new file mode 100644 index 000000000..ca46368ed --- /dev/null +++ b/.github/workflows/repair-pr939-lock-path.yml @@ -0,0 +1,139 @@ +name: Repair PR 939 nested requirements lock discovery + +on: + push: + branches: + - codex/fix-cross-repo-opencode-evidence + paths: + - .github/workflows/repair-pr939-lock-path.yml + +permissions: + contents: write + +concurrency: + group: repair-pr939-lock-path + cancel-in-progress: false + +jobs: + repair: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check out the exact repair branch + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + with: + ref: codex/fix-cross-repo-opencode-evidence + fetch-depth: 0 + + - name: Write the failing nested-path regression contract + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + cat > tests/test_materialize_base_python_requirement_paths.py <<'PY' + """Regression contracts for repository-relative requirements lock discovery.""" + + from __future__ import annotations + + import subprocess + import tempfile + import unittest + from pathlib import Path + + from scripts.ci import materialize_base_python_requirements as materializer + + + def _git(repo: Path, *args: str) -> str: + """Run one deterministic Git command in the fixture repository.""" + return subprocess.run( + ["git", "-C", str(repo), *args], + check=True, + capture_output=True, + text=True, + ).stdout.strip() + + + class BaseHashLockPathTests(unittest.TestCase): + """Protect direct ``requirements`` directory child discovery.""" + + def test_collects_hash_locks_under_any_requirements_directory(self) -> None: + """Use the complete repository-relative path, not only the basename.""" + with tempfile.TemporaryDirectory() as temporary_directory: + repo = Path(temporary_directory) + _git(repo, "init") + _git(repo, "config", "user.name", "Test") + _git(repo, "config", "user.email", "test@example.invalid") + + root_requirements = repo / "requirements" + nested_requirements = repo / "service" / "requirements" + root_requirements.mkdir() + nested_requirements.mkdir(parents=True) + (root_requirements / "ci.txt").write_text( + "ci-demo==1 --hash=sha256:" + ("a" * 64) + "\n", + encoding="utf-8", + ) + (nested_requirements / "package.txt").write_text( + "service-demo==1 --hash=sha256:" + ("b" * 64) + "\n", + encoding="utf-8", + ) + _git(repo, "add", ".") + _git(repo, "commit", "-m", "base") + base_sha = _git(repo, "rev-parse", "HEAD") + + locks = materializer.base_hash_locks(repo, base_sha) + + self.assertEqual( + [path for path, _content in locks], + [ + "requirements/ci.txt", + "service/requirements/package.txt", + ], + ) + + + if __name__ == "__main__": + unittest.main() + PY + + if python3 tests/test_materialize_base_python_requirement_paths.py; then + echo "ERROR: nested requirements path contract unexpectedly passed before the fix." >&2 + exit 1 + fi + + - name: Use the repository-relative candidate path + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python3 - <<'PY' + from pathlib import Path + + path = Path("scripts/ci/materialize_base_python_requirements.py") + source = path.read_text(encoding="utf-8") + old = " if _is_candidate_lock_name(candidate.name):\n" + new = " if _is_candidate_lock_path(candidate):\n" + count = source.count(old) + if count != 1: + raise SystemExit(f"expected one base lock discovery anchor, found {count}") + path.write_text(source.replace(old, new, 1), encoding="utf-8") + PY + + - name: Verify the focused repair + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python3 tests/test_materialize_base_python_requirement_paths.py + python3 -m compileall -q \ + scripts/ci/materialize_base_python_requirements.py \ + tests/test_materialize_base_python_requirement_paths.py + git diff --check + + - name: Commit the verified repair and remove this workflow + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + git fetch origin codex/fix-cross-repo-opencode-evidence + test "$(git rev-parse HEAD)" = "$(git rev-parse origin/codex/fix-cross-repo-opencode-evidence)" + rm -- .github/workflows/repair-pr939-lock-path.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add \ + scripts/ci/materialize_base_python_requirements.py \ + tests/test_materialize_base_python_requirement_paths.py \ + .github/workflows/repair-pr939-lock-path.yml + git commit -m "fix(ci): discover nested requirements lock paths" + git push origin HEAD:codex/fix-cross-repo-opencode-evidence From 4470fc930bb7f06b0ee567bac2e6918ecc70a1a5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 18:21:54 +0900 Subject: [PATCH 17/22] ci: route lock-path repair to ubuntu-24.04 --- .github/workflows/repair-pr939-lock-path.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/repair-pr939-lock-path.yml b/.github/workflows/repair-pr939-lock-path.yml index ca46368ed..9a6d28d25 100644 --- a/.github/workflows/repair-pr939-lock-path.yml +++ b/.github/workflows/repair-pr939-lock-path.yml @@ -12,11 +12,11 @@ permissions: concurrency: group: repair-pr939-lock-path - cancel-in-progress: false + cancel-in-progress: true jobs: repair: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 15 steps: - name: Check out the exact repair branch From 912313ff92cdcee6f240e9584f79ca37615ee5a2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 18:24:04 +0900 Subject: [PATCH 18/22] ci: execute lock-path regression from repository root --- .github/workflows/repair-pr939-lock-path.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/repair-pr939-lock-path.yml b/.github/workflows/repair-pr939-lock-path.yml index 9a6d28d25..315db13f1 100644 --- a/.github/workflows/repair-pr939-lock-path.yml +++ b/.github/workflows/repair-pr939-lock-path.yml @@ -93,7 +93,7 @@ jobs: unittest.main() PY - if python3 tests/test_materialize_base_python_requirement_paths.py; then + if PYTHONPATH=. python3 tests/test_materialize_base_python_requirement_paths.py; then echo "ERROR: nested requirements path contract unexpectedly passed before the fix." >&2 exit 1 fi @@ -117,7 +117,7 @@ jobs: - name: Verify the focused repair shell: bash --noprofile --norc -e -o pipefail {0} run: | - python3 tests/test_materialize_base_python_requirement_paths.py + PYTHONPATH=. python3 tests/test_materialize_base_python_requirement_paths.py python3 -m compileall -q \ scripts/ci/materialize_base_python_requirements.py \ tests/test_materialize_base_python_requirement_paths.py From 0d90922350dd88c02aa1412625263364ad3546f2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 09:24:20 +0000 Subject: [PATCH 19/22] fix(ci): discover nested requirements lock paths --- .github/workflows/repair-pr939-lock-path.yml | 139 ------------------ .../materialize_base_python_requirements.py | 2 +- ...terialize_base_python_requirement_paths.py | 62 ++++++++ 3 files changed, 63 insertions(+), 140 deletions(-) delete mode 100644 .github/workflows/repair-pr939-lock-path.yml create mode 100644 tests/test_materialize_base_python_requirement_paths.py diff --git a/.github/workflows/repair-pr939-lock-path.yml b/.github/workflows/repair-pr939-lock-path.yml deleted file mode 100644 index 315db13f1..000000000 --- a/.github/workflows/repair-pr939-lock-path.yml +++ /dev/null @@ -1,139 +0,0 @@ -name: Repair PR 939 nested requirements lock discovery - -on: - push: - branches: - - codex/fix-cross-repo-opencode-evidence - paths: - - .github/workflows/repair-pr939-lock-path.yml - -permissions: - contents: write - -concurrency: - group: repair-pr939-lock-path - cancel-in-progress: true - -jobs: - repair: - runs-on: ubuntu-24.04 - timeout-minutes: 15 - steps: - - name: Check out the exact repair branch - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - with: - ref: codex/fix-cross-repo-opencode-evidence - fetch-depth: 0 - - - name: Write the failing nested-path regression contract - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - cat > tests/test_materialize_base_python_requirement_paths.py <<'PY' - """Regression contracts for repository-relative requirements lock discovery.""" - - from __future__ import annotations - - import subprocess - import tempfile - import unittest - from pathlib import Path - - from scripts.ci import materialize_base_python_requirements as materializer - - - def _git(repo: Path, *args: str) -> str: - """Run one deterministic Git command in the fixture repository.""" - return subprocess.run( - ["git", "-C", str(repo), *args], - check=True, - capture_output=True, - text=True, - ).stdout.strip() - - - class BaseHashLockPathTests(unittest.TestCase): - """Protect direct ``requirements`` directory child discovery.""" - - def test_collects_hash_locks_under_any_requirements_directory(self) -> None: - """Use the complete repository-relative path, not only the basename.""" - with tempfile.TemporaryDirectory() as temporary_directory: - repo = Path(temporary_directory) - _git(repo, "init") - _git(repo, "config", "user.name", "Test") - _git(repo, "config", "user.email", "test@example.invalid") - - root_requirements = repo / "requirements" - nested_requirements = repo / "service" / "requirements" - root_requirements.mkdir() - nested_requirements.mkdir(parents=True) - (root_requirements / "ci.txt").write_text( - "ci-demo==1 --hash=sha256:" + ("a" * 64) + "\n", - encoding="utf-8", - ) - (nested_requirements / "package.txt").write_text( - "service-demo==1 --hash=sha256:" + ("b" * 64) + "\n", - encoding="utf-8", - ) - _git(repo, "add", ".") - _git(repo, "commit", "-m", "base") - base_sha = _git(repo, "rev-parse", "HEAD") - - locks = materializer.base_hash_locks(repo, base_sha) - - self.assertEqual( - [path for path, _content in locks], - [ - "requirements/ci.txt", - "service/requirements/package.txt", - ], - ) - - - if __name__ == "__main__": - unittest.main() - PY - - if PYTHONPATH=. python3 tests/test_materialize_base_python_requirement_paths.py; then - echo "ERROR: nested requirements path contract unexpectedly passed before the fix." >&2 - exit 1 - fi - - - name: Use the repository-relative candidate path - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python3 - <<'PY' - from pathlib import Path - - path = Path("scripts/ci/materialize_base_python_requirements.py") - source = path.read_text(encoding="utf-8") - old = " if _is_candidate_lock_name(candidate.name):\n" - new = " if _is_candidate_lock_path(candidate):\n" - count = source.count(old) - if count != 1: - raise SystemExit(f"expected one base lock discovery anchor, found {count}") - path.write_text(source.replace(old, new, 1), encoding="utf-8") - PY - - - name: Verify the focused repair - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - PYTHONPATH=. python3 tests/test_materialize_base_python_requirement_paths.py - python3 -m compileall -q \ - scripts/ci/materialize_base_python_requirements.py \ - tests/test_materialize_base_python_requirement_paths.py - git diff --check - - - name: Commit the verified repair and remove this workflow - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - git fetch origin codex/fix-cross-repo-opencode-evidence - test "$(git rev-parse HEAD)" = "$(git rev-parse origin/codex/fix-cross-repo-opencode-evidence)" - rm -- .github/workflows/repair-pr939-lock-path.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add \ - scripts/ci/materialize_base_python_requirements.py \ - tests/test_materialize_base_python_requirement_paths.py \ - .github/workflows/repair-pr939-lock-path.yml - git commit -m "fix(ci): discover nested requirements lock paths" - git push origin HEAD:codex/fix-cross-repo-opencode-evidence diff --git a/scripts/ci/materialize_base_python_requirements.py b/scripts/ci/materialize_base_python_requirements.py index 311b39e38..7b5598b56 100755 --- a/scripts/ci/materialize_base_python_requirements.py +++ b/scripts/ci/materialize_base_python_requirements.py @@ -518,7 +518,7 @@ def base_hash_locks(repo_root: pathlib.Path, base_sha: str) -> list[tuple[str, b regular_paths = {path for path, _candidate in regular_blobs} locks: list[tuple[str, bytes]] = [] for path, candidate in regular_blobs: - if _is_candidate_lock_name(candidate.name): + if _is_candidate_lock_path(candidate): content = _git(repo_root, "show", f"{base_sha}:{path}") if _is_hash_pinned(content): locks.append((path, content)) diff --git a/tests/test_materialize_base_python_requirement_paths.py b/tests/test_materialize_base_python_requirement_paths.py new file mode 100644 index 000000000..fbf0e5633 --- /dev/null +++ b/tests/test_materialize_base_python_requirement_paths.py @@ -0,0 +1,62 @@ +"""Regression contracts for repository-relative requirements lock discovery.""" + +from __future__ import annotations + +import subprocess +import tempfile +import unittest +from pathlib import Path + +from scripts.ci import materialize_base_python_requirements as materializer + + +def _git(repo: Path, *args: str) -> str: + """Run one deterministic Git command in the fixture repository.""" + return subprocess.run( + ["git", "-C", str(repo), *args], + check=True, + capture_output=True, + text=True, + ).stdout.strip() + + +class BaseHashLockPathTests(unittest.TestCase): + """Protect direct ``requirements`` directory child discovery.""" + + def test_collects_hash_locks_under_any_requirements_directory(self) -> None: + """Use the complete repository-relative path, not only the basename.""" + with tempfile.TemporaryDirectory() as temporary_directory: + repo = Path(temporary_directory) + _git(repo, "init") + _git(repo, "config", "user.name", "Test") + _git(repo, "config", "user.email", "test@example.invalid") + + root_requirements = repo / "requirements" + nested_requirements = repo / "service" / "requirements" + root_requirements.mkdir() + nested_requirements.mkdir(parents=True) + (root_requirements / "ci.txt").write_text( + "ci-demo==1 --hash=sha256:" + ("a" * 64) + "\n", + encoding="utf-8", + ) + (nested_requirements / "package.txt").write_text( + "service-demo==1 --hash=sha256:" + ("b" * 64) + "\n", + encoding="utf-8", + ) + _git(repo, "add", ".") + _git(repo, "commit", "-m", "base") + base_sha = _git(repo, "rev-parse", "HEAD") + + locks = materializer.base_hash_locks(repo, base_sha) + + self.assertEqual( + [path for path, _content in locks], + [ + "requirements/ci.txt", + "service/requirements/package.txt", + ], + ) + + +if __name__ == "__main__": + unittest.main() From 38cb3b9c4dd9febc475ecdc2f71c0c354032be5c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 18:26:07 +0900 Subject: [PATCH 20/22] docs(ci): record base requirements lock discovery contract --- .../base-requirements-lock-discovery.md | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 docs/doctoring/base-requirements-lock-discovery.md diff --git a/docs/doctoring/base-requirements-lock-discovery.md b/docs/doctoring/base-requirements-lock-discovery.md new file mode 100644 index 000000000..66e7efc9e --- /dev/null +++ b/docs/doctoring/base-requirements-lock-discovery.md @@ -0,0 +1,59 @@ +# Base requirements lock discovery contract + +## Purpose + +This doctoring record defines how the central review and coverage workflows discover hash-pinned Python requirement locks from an authenticated pull-request base commit. It records the nested-path regression repaired in pull request #939 and preserves the security boundary already developed in pull request #785. + +## Incident + +The materializer intentionally recognizes two candidate forms: + +- conventional file names such as `requirements.txt`, `requirements-dev.txt`, and `requirements.lock`; and +- direct `.txt` children of any directory named `requirements`, such as `requirements/ci.txt` and `service/requirements/package.txt`. + +The path predicate implemented both forms, but `base_hash_locks()` still called the basename-only predicate. As a result, a direct child such as `requirements/ci.txt` was rejected before its authenticated base blob and hash-pinned content could be evaluated. The implementation advertised path-aware eligibility while the collector enforced only legacy basename eligibility. + +The repair changes the collector to call `_is_candidate_lock_path(candidate)` with the already parsed `PurePosixPath`. It does not broaden the accepted Git object types or relax content validation. + +## Trust boundary + +A candidate enters the generated build context only when every applicable condition holds: + +1. The base revision is an exact 40-character hexadecimal commit SHA. +2. `git ls-tree` reports a regular `100...` blob in that exact base tree. +3. The repository-relative path is non-absolute and contains no `..` component. +4. The path is either a conventional requirements lock name or a direct `.txt` child of a directory named `requirements`. +5. Every substantive requirement is an exact `==` pin with complete SHA-256 hashes, or a separately bounded relative requirements include. +6. Symlinks, gitlinks, malformed tree entries, unpinned files, unsafe includes, and pull-request-only content remain excluded. +7. `uv.lock` follows its separate trusted export path and still requires the corresponding base-owned `pyproject.toml`. + +Path eligibility is candidate discovery, not dependency trust. The existing hash, include, export, and downstream closure checks remain authoritative. + +## Test-first evidence + +Temporary repair workflow run `31787913977` executed the following sequence on head `912313ff92cdcee6f240e9584f79ca37615ee5a2`: + +1. Created a temporary Git repository containing hash-pinned `requirements/ci.txt` and `service/requirements/package.txt` blobs. +2. Confirmed the regression test failed before the implementation change because neither path was collected. +3. Replaced the basename-only collector predicate with the repository-relative path predicate. +4. Confirmed both paths were returned in deterministic repository order. +5. Compiled the implementation and regression test and ran `git diff --check`. +6. Deleted the temporary writer workflow before committing the production change. + +An earlier repair attempt failed before exercising the assertion because direct script execution omitted the repository root from `sys.path`. The corrected workflow ran both RED and GREEN phases with the same explicit `PYTHONPATH=.` environment, so the observed transition is attributable to the collector change rather than import setup. + +## Permanent regression command + +```bash +PYTHONPATH=. python3 tests/test_materialize_base_python_requirement_paths.py +python3 -m compileall -q \ + scripts/ci/materialize_base_python_requirements.py \ + tests/test_materialize_base_python_requirement_paths.py +git diff --check +``` + +The repository quality workflow must also run the full materializer and Strix regression suites on the exact pull-request head. Focused repair evidence cannot replace protected-branch checks, semantic review, or required independent approvals. + +## Change-management rule + +Future changes to candidate naming, path parsing, Git tree filtering, requirement includes, `uv.lock` export, or materialized manifests must update the path-discovery tests and the broader materializer suite together. A path predicate and its collector call site must not evolve independently. From aae3d9e0b0e24ce85f5e6af98e9e1cff518c928e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 14:44:51 +0000 Subject: [PATCH 21/22] fix(ci): stop cancelled required checks from looking failed Do not cancel in-flight required scan-pr-queue runs, and run the OpenCode required-workflow stub jobs in parallel, so a later same-head success is not hidden behind a cancelled or queued required check. Co-authored-by: Seongho Bae --- .github/workflows/opencode-review.yml | 3 --- .../workflows/pr-review-merge-scheduler.yml | 5 ++++- CHANGELOG.md | 1 + scripts/ci/test_strix_quick_gate.sh | 2 +- .../test_required_workflow_queue_contract.py | 22 +++++++++++++++++++ 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index 7f1ad6d00..80ed61440 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -33,7 +33,6 @@ jobs: coverage-source-tree: name: coverage-source-tree - needs: [required-workflow-bootstrap] runs-on: ubuntu-latest steps: - run: >- @@ -42,7 +41,6 @@ jobs: coverage-evidence: name: coverage-evidence - needs: [coverage-source-tree] runs-on: ubuntu-latest steps: - run: >- @@ -51,7 +49,6 @@ jobs: opencode-review-target: name: opencode-review - needs: [coverage-evidence] runs-on: ubuntu-latest steps: - run: >- diff --git a/.github/workflows/pr-review-merge-scheduler.yml b/.github/workflows/pr-review-merge-scheduler.yml index 8e1157060..5882b4fd4 100644 --- a/.github/workflows/pr-review-merge-scheduler.yml +++ b/.github/workflows/pr-review-merge-scheduler.yml @@ -99,7 +99,10 @@ concurrency: github.event_name == 'repository_dispatch' && github.event.client_payload.pr_number != '' && format('pr-{0}', github.event.client_payload.pr_number) || github.event_name == 'repository_dispatch' && github.run_id || github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request_target' || github.event_name == 'pull_request_review' || github.event_name == 'repository_dispatch' }} + # scan-pr-queue is a required check. Cancelling an in-flight same-head run + # leaves a CANCELLED required conclusion that stays in the rollup after a + # later success, so the PR looks failed with zero failed jobs. + cancel-in-progress: false # Scorecard Token-Permissions (alert #9): declare a least-privilege default at # the workflow level. The scan-pr-queue job that actually needs write access diff --git a/CHANGELOG.md b/CHANGELOG.md index 835b5984f..923d67847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Semantic Versioning where the repository publishes a release. ### Fixed - Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context. +- Stopped cancelling in-flight required `scan-pr-queue` runs and stopped serializing the OpenCode required-workflow stub jobs, so a later same-head success is not hidden behind a cancelled or queued required check. - Sent a static User-Agent on the pinned trusted-uv archive request, required an exact-head formal OpenCode review before skipping unavailable cross-repository commit-status publication, and kept Strix failed when provider evidence is incomplete instead of neutralizing an outage into a pass. - Refused a conflict-scope repository root whose immediate parent is a symbolic link, so a swapped parent cannot redirect the canonical worktree after the last-component check (CWE-367). - Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics. diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 7343c06ac..a35066687 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -1506,7 +1506,7 @@ assert_pr_review_merge_scheduler_uses_github_actions_bot_token() { assert_file_contains "$workflow_file" "github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number && format('pr-{0}', github.event.workflow_run.pull_requests[0].number)" "scheduler scopes workflow_run concurrency to the completed review PR" assert_file_contains "$workflow_file" "github.event_name == 'schedule' && format('schedule-{0}', github.event.schedule)" "scheduler isolates the 15-minute organization sweep from the separate 30-minute scheduled scan" assert_file_contains "$workflow_file" "github.event_name == 'repository_dispatch' && github.run_id" "scheduler keeps manual queue scans isolated per run" - assert_file_contains "$workflow_file" "cancel-in-progress: \${{ github.event_name == 'pull_request_target' || github.event_name == 'pull_request_review' || github.event_name == 'repository_dispatch' }}" "scheduler cancels stale PR/review/manual queue scans instead of accumulating merge/update attempts" + assert_file_contains "$workflow_file" "cancel-in-progress: false" "scheduler does not cancel in-progress required scan-pr-queue runs" assert_file_contains "$workflow_file" "timeout-minutes: 60" "organization sweep has enough headroom to finish the complete repository walk" assert_file_contains "$workflow_file" "ORG_SWEEP_TRIGGER_REVIEWS: \${{ github.event_name == 'schedule' ||" "scheduled organization sweeps retry missing current-head OpenCode reviews" assert_file_contains "$workflow_file" "ORG_SWEEP_ENABLE_AUTO_MERGE: \${{ github.event_name == 'schedule' ||" "scheduled organization sweeps merge approved current heads" diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index c7a066de3..e42049085 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -296,6 +296,28 @@ def test_close_empty_pr_metadata_lookup_retries_and_fails_open() -> None: assert "exit 0" in workflow +def test_merge_scheduler_does_not_cancel_required_queue_scans() -> None: + """A cancelled required scan-pr-queue stays red after later same-head success.""" + workflow = workflow_text("pr-review-merge-scheduler.yml") + concurrency = workflow.split("concurrency:", 1)[1].split("permissions:", 1)[0] + + assert "cancel-in-progress: false" in concurrency + assert "cancel-in-progress: true" not in concurrency + assert "leaves a CANCELLED required conclusion" in concurrency + + +def test_opencode_bootstrap_required_checks_do_not_serialize_runner_waits() -> None: + """Required stub names must not wait on each other for a runner.""" + bootstrap = workflow_text("opencode-review.yml") + jobs = bootstrap.split("jobs:\n", 1)[1] + + assert " required-workflow-bootstrap:\n" in jobs + assert " coverage-source-tree:\n" in jobs + assert " coverage-evidence:\n" in jobs + assert " opencode-review-target:\n" in jobs + assert " needs:" not in jobs + + def test_cancelled_review_workflow_runs_do_not_spawn_more_queue_work() -> None: for filename in ("noema-review.yml", "pr-review-merge-scheduler.yml"): workflow = workflow_text(filename) From f74e4f7114487adcd77f24b9fc1a27505d92d46c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 14:45:21 +0000 Subject: [PATCH 22/22] test(ci): pin merge scheduler to keep required queue scans Update the leftover concurrency assertion so it requires cancel-in-progress: false instead of the cancelled-required-check expression. Co-authored-by: Seongho Bae --- tests/test_opencode_agent_contract.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 34c685f33..bea2e0cc5 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -1994,10 +1994,7 @@ def test_merge_scheduler_uses_escalating_mutation_credentials(): assert 'check_delay="$((check_attempt * 2))"' in workflow assert "steps.review_followup.outputs.proceed != 'false'" in workflow assert "The scheduled organization sweep remains authoritative." in workflow - assert ( - "github.event_name == 'pull_request_review' || " - "github.event_name == 'repository_dispatch'" in workflow - ) + assert "cancel-in-progress: false" in workflow def test_opencode_runs_merge_scheduler_after_review_without_repo_local_dispatch():