From c169b146cfaaebd2ba75ee781bfcf1d82f4dbb84 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 10:29:08 +0900 Subject: [PATCH 01/13] fix(strix): align fallback model contract --- .github/workflows/opencode-review-dispatch.yml | 12 ++++-------- CHANGELOG.md | 6 ++++++ docs/adr/0002-product-technical-gap-baseline.md | 12 ++++++++++++ .../doctoring/strix-nvidia-nim-not-found-fallback.md | 9 +++++++++ docs/product-technical-gap-baseline.md | 12 ++++++++++++ .../emit_opencode_failed_check_fallback_findings.sh | 2 +- scripts/ci/strix_required_workflow_smoke.sh | 4 ++-- tests/test_opencode_agent_contract.py | 8 ++++---- tests/test_required_workflow_queue_contract.py | 2 +- tests/test_strix_nvidia_nim_not_found_fallback.py | 4 ++-- 10 files changed, 53 insertions(+), 18 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index dd65d90e1..ed3f7b44f 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -4058,8 +4058,8 @@ jobs: "apiKey": "{env:OPENAI_API_KEY}" }, "models": { - "gpt-5.6-luna": { - "name": "OpenAI GPT-5.6 Luna (direct)", + "gpt-5.4": { + "name": "OpenAI GPT-5.4 (direct)", "tool_call": true, "reasoning": true, "options": { @@ -4471,17 +4471,13 @@ jobs: # or used for product/model improvement, so private repositories # include neither NIM nor anonymous free candidates and start at the # existing keyed fallback list: OpenCode Zen GPT-5.6 Terra, DeepSeek - # V3, the direct GPT-5.6 Luna slot, and pinned PAID + # V3, the direct GPT-5.4 slot, and pinned PAID # OpenRouter coder models (free-tier candidates hit the shared # free-models-per-day cap and hung for the full candidate timeout, # so the OpenRouter slots use cheap paid models billed against the # org's OpenRouter credits), then the full-size GPT-4.1 long-context # endpoint and provider-specific GPT/o3 fallbacks. - # The direct-OpenAI slot runs GPT-5.6 Luna: the newest family's - # cost-efficient tier, cheaper than the legacy gpt-5 it replaced - # ($1/$6 vs $1.25/$10 per 1M tokens) so the org OpenAI budget - # stretches further between top-ups. - OPENCODE_MODEL_CANDIDATES: "${{ needs.validate-pr-metadata.outputs.is_private == 'false' && 'nvidia-nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 nvidia-nim/nvidia/llama-3.1-nemotron-ultra-253b-v1 nvidia-nim/nvidia/nemotron-3-super-120b-a12b nvidia-nim/nvidia/nemotron-3-ultra-550b-a55b nvidia-nim/meta/llama-3.3-70b-instruct nvidia-nim/deepseek-ai/deepseek-v4-pro nvidia-nim/mistralai/codestral-22b-instruct-v0.1 opencode-free/nemotron-3-ultra-free opencode-free/deepseek-v4-flash-free opencode-free/north-mini-code-free opencode-free/laguna-s-2.1-free opencode-free/ling-3.0-flash-free opencode-free/big-pickle opencode-free/mimo-v2.5-free opencode-free/hy3-free opencode-free/minimax-m3-free opencode-free/glm-5-free opencode-free/kimi-k2.5-free opencode-free/qwen3.6-plus-free ' || '' }}opencode/gpt-5.6-terra github-models/deepseek/deepseek-v3-0324 openai/gpt-5.6-luna openrouter/deepseek/deepseek-v3.2 openrouter/qwen/qwen3-coder github-models/openai/gpt-4.1 github-models/openai/gpt-5 github-models/openai/gpt-5-chat github-models/openai/o3 github-models/deepseek/deepseek-r1-0528 github-models/deepseek/deepseek-r1" + OPENCODE_MODEL_CANDIDATES: "${{ needs.validate-pr-metadata.outputs.is_private == 'false' && 'nvidia-nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 nvidia-nim/nvidia/llama-3.1-nemotron-ultra-253b-v1 nvidia-nim/nvidia/nemotron-3-super-120b-a12b nvidia-nim/nvidia/nemotron-3-ultra-550b-a55b nvidia-nim/meta/llama-3.3-70b-instruct nvidia-nim/deepseek-ai/deepseek-v4-pro nvidia-nim/mistralai/codestral-22b-instruct-v0.1 opencode-free/nemotron-3-ultra-free opencode-free/deepseek-v4-flash-free opencode-free/north-mini-code-free opencode-free/laguna-s-2.1-free opencode-free/ling-3.0-flash-free opencode-free/big-pickle opencode-free/mimo-v2.5-free opencode-free/hy3-free opencode-free/minimax-m3-free opencode-free/glm-5-free opencode-free/kimi-k2.5-free opencode-free/qwen3.6-plus-free ' || '' }}opencode/gpt-5.6-terra github-models/deepseek/deepseek-v3-0324 openai/gpt-5.4 openrouter/deepseek/deepseek-v3.2 openrouter/qwen/qwen3-coder github-models/openai/gpt-4.1 github-models/openai/gpt-5 github-models/openai/gpt-5-chat github-models/openai/o3 github-models/deepseek/deepseek-r1-0528 github-models/deepseek/deepseek-r1" # One attempt per model, then fall through to the next model. Retrying # the SAME model 5x let a rate-limited/hung leader consume the whole # step, so the pool never reached a healthy fallback model. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1630c32d4..c5930034a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,12 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Reconciled the Strix required-workflow smoke contract and the privileged + OpenCode model pool with the current `gpt-5.4` direct-OpenAI fallback after + `gpt-5.6-luna` was retired. This prevents every consumer repository's + required Strix check from failing on a stale central assertion or selecting a + nonexistent direct model. + - Publish only the sanitized cumulative Strix report tree, avoiding a later copy of relative scanner output that could reintroduce known internal warning text into uploaded security evidence. diff --git a/docs/adr/0002-product-technical-gap-baseline.md b/docs/adr/0002-product-technical-gap-baseline.md index 30f966c0c..4caf92257 100644 --- a/docs/adr/0002-product-technical-gap-baseline.md +++ b/docs/adr/0002-product-technical-gap-baseline.md @@ -7,3 +7,15 @@ - Ownership: .github owns control-plane evidence; naruon and product repositories own product behavior and consumer smoke. - Figma File ID: N/A. This repository has no customer UI. A UI-owning repository must replace N/A with its real Figma File ID before a UI PR is accepted and must provide Storybook and design-token evidence. - Consequence: The document is an operational snapshot, not a merge authorization or substitute for protected GitHub review. Hourly agents must re-collect exact head SHAs, reviews, threads, and required Checks before merge. Papers/standards live in `docs/doctoring/product-technical-gap-baseline.md` and must remain consistent with this ADR. + +## Amendment: central Strix fallback contract (2026-08-25) + +The current `main` workflow (`a724582`) intentionally replaced the unavailable +direct-OpenAI `gpt-5.6-luna` fallback with `gpt-5.4`, but the required-workflow +smoke script still asserted the retired model. The privileged OpenCode model +pool also retained the retired candidate while its contract tests had already +moved to `gpt-5.4`. This mismatch failed consumer Strix checks, including +ContextualWisdomLab/disksage#247, before any target-repository security +analysis ran. The workflow, smoke contract, model-pool configuration, and +regression tests now share `gpt-5.4`; the change does not weaken provider +failure or vulnerability fail-closed behavior. diff --git a/docs/doctoring/strix-nvidia-nim-not-found-fallback.md b/docs/doctoring/strix-nvidia-nim-not-found-fallback.md index a088aa7ef..213429e01 100644 --- a/docs/doctoring/strix-nvidia-nim-not-found-fallback.md +++ b/docs/doctoring/strix-nvidia-nim-not-found-fallback.md @@ -64,6 +64,15 @@ current NVIDIA documentation whenever a provider returns a catalog 404. This change does not treat arbitrary provider errors as success and does not weaken Strix severity, changed-file attribution, or independent approval requirements. +## Current fallback contract (2026-08-25) + +The direct-OpenAI fallback is `gpt-5.4`. The retired `gpt-5.6-luna` identifier +must not appear in the executable workflow, required smoke contract, or model +pool. A central workflow update without its smoke and model-pool assertions is +invalid because every consumer repository would fail before its own scan. The +contract is verified by `scripts/ci/strix_required_workflow_smoke.sh` and the +focused `test_strix_quick_gate.sh` case; provider failures remain non-passing. + ## References Fielding, R., Nottingham, M., & Reschke, J. (2022). *HTTP semantics* (RFC diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 1d884233f..252249095 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -204,6 +204,18 @@ flowchart LR - Open count is 98. No additional `.github` PR merged this pass. +## 2026-08-25 central Strix fallback contract recheck + +- `main` at `a724582a0768129d481385070bf8f05b2620dd2c` changed the direct-OpenAI + fallback to `gpt-5.4`, but the required-workflow smoke script still required + the retired `gpt-5.6-luna` string. The privileged OpenCode model pool also + retained the retired candidate while its contract tests expected `gpt-5.4`. +- This exact mismatch caused consumer Strix checks to fail before scanning the + target repository; it was observed on ContextualWisdomLab/disksage#247 at + exact head `a9c868a6e9c8d68a9c6ea6de381e188740b8f5db`. The focused repair keeps + provider errors and vulnerability findings fail-closed and only aligns the + executable model and its assertions. + ## 5. 실행 루프와 고객의 다음 행동 각 hourly pass는 아래 순서를 유지한다. diff --git a/scripts/ci/emit_opencode_failed_check_fallback_findings.sh b/scripts/ci/emit_opencode_failed_check_fallback_findings.sh index ccd35a273..b8a755e6e 100755 --- a/scripts/ci/emit_opencode_failed_check_fallback_findings.sh +++ b/scripts/ci/emit_opencode_failed_check_fallback_findings.sh @@ -956,7 +956,7 @@ extract_strix_failed_check_block "$EVIDENCE_FILE" "$strix_evidence_file" emit_known_missing_string_finding \ "$EVIDENCE_FILE" \ - "steps.target_visibility.outputs.is_private == 'false' && 'nvidia_nim/nvidia/nemotron-3-ultra-550b-a55b' || 'gpt-5.6-luna'" \ + "steps.target_visibility.outputs.is_private == 'false' && 'nvidia_nim/nvidia/nemotron-3-ultra-550b-a55b' || 'gpt-5.4'" \ "Strix public scans must default to NVIDIA NIM while private scans retain the contracted provider" \ ".github/workflows/strix.yml" \ "scripts/ci/test_strix_quick_gate.sh" diff --git a/scripts/ci/strix_required_workflow_smoke.sh b/scripts/ci/strix_required_workflow_smoke.sh index 8539afdfe..76aec7910 100755 --- a/scripts/ci/strix_required_workflow_smoke.sh +++ b/scripts/ci/strix_required_workflow_smoke.sh @@ -170,8 +170,8 @@ assert_file_contains "$full_gate_test" "assert_strix_workflow_pr_trigger_hardene assert_file_contains "$workflow_file" "nvidia_nim/nvidia/nemotron-3-super-120b-a12b" "Strix defaults public scans to the current hosted NVIDIA NIM model" assert_file_contains_either \ "$workflow_file" \ - "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 openai_direct/gpt-5.6-luna" \ - "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 openai-direct/gpt-5.6-luna" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 openai_direct/gpt-5.4" \ + "nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 openai-direct/gpt-5.4" \ "Strix tries another NVIDIA hosted model before falling back to direct OpenAI" assert_file_not_contains "$workflow_file" "github_models/openai/o3" "Strix fallback list must not depend on GitHub Models, which is in platform-wide retirement" assert_file_contains "$workflow_file" "Nvidia_nimException" "Strix workflow recognizes provider-scoped NVIDIA NIM failures" diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 8ee6e86fc..2360fdb26 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -186,7 +186,7 @@ def test_opencode_model_pool_sets_high_effort_for_capable_candidates(): ["opencode-free", "qwen3.6-plus-free"], ["opencode", "gpt-5.6-terra"], ["github-models", "deepseek/deepseek-v3-0324"], - ["openai", "gpt-5.6-luna"], + ["openai", "gpt-5.4"], ["openrouter", "deepseek/deepseek-v3.2"], ["openrouter", "qwen/qwen3-coder"], ["github-models", "openai/gpt-4.1"], @@ -197,7 +197,7 @@ def test_opencode_model_pool_sets_high_effort_for_capable_candidates(): ["github-models", "deepseek/deepseek-r1"], ] assert zen_models == ["gpt-5.6-terra"] - assert direct_openai_models == ["gpt-5.6-luna"] + assert direct_openai_models == ["gpt-5.4"] assert openrouter_models == [ "deepseek/deepseek-v3.2", "qwen/qwen3-coder", @@ -1740,7 +1740,7 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): assert ( "opencode/gpt-5.6-terra " "github-models/deepseek/deepseek-v3-0324 " - "openai/gpt-5.6-luna " + "openai/gpt-5.4 " "openrouter/deepseek/deepseek-v3.2 " "openrouter/qwen/qwen3-coder " "github-models/openai/gpt-4.1 " @@ -1887,7 +1887,7 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): ) assert ( "github-models/deepseek/deepseek-v3-0324 " - "openai/gpt-5.6-luna " + "openai/gpt-5.4 " "openrouter/deepseek/deepseek-v3.2 " "openrouter/qwen/qwen3-coder " "github-models/openai/gpt-4.1 " diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index e58f5e6c0..1d79f1daa 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -505,7 +505,7 @@ def test_nvidia_nim_defaults_preserve_existing_fallbacks_without_secret( assert strix.returncode == 0, strix.stderr assert { "provider_mode=openai_direct", - "strix_model=gpt-5.6-luna", + "strix_model=gpt-5.4", } <= set(strix_output.read_text().splitlines()) assert ( "STRIX_MODEL: ${{ steps.gate.outputs.strix_model }}" diff --git a/tests/test_strix_nvidia_nim_not_found_fallback.py b/tests/test_strix_nvidia_nim_not_found_fallback.py index 990269725..17f0e9a30 100644 --- a/tests/test_strix_nvidia_nim_not_found_fallback.py +++ b/tests/test_strix_nvidia_nim_not_found_fallback.py @@ -192,7 +192,7 @@ def test_workflow_uses_available_free_first_nvidia_plan(self) -> None: workflow = STRIX_WORKFLOW.read_text(encoding="utf-8") default_expression = ( "steps.target_visibility.outputs.is_private == 'false' && " - f"'{DEFAULT_NVIDIA_MODEL}' || 'gpt-5.6-luna'" + f"'{DEFAULT_NVIDIA_MODEL}' || 'gpt-5.4'" ) self.assertIn(default_expression, workflow) self.assertIn( @@ -202,7 +202,7 @@ def test_workflow_uses_available_free_first_nvidia_plan(self) -> None: ) self.assertIn( "steps.gate.outputs.provider_mode == 'nvidia_nim' && " - f"'{FREE_NVIDIA_FALLBACK} openai-direct/gpt-5.6-luna'", + f"'{FREE_NVIDIA_FALLBACK} openai-direct/gpt-5.4'", workflow, ) From c3eb391bdf5231c09117def0665b631877c5f636 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 10:32:06 +0900 Subject: [PATCH 02/13] test(strix): refresh dispatch workflow blob pin --- tests/test_pr_review_autofix_nvidia_nim_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 799b9e9fb..16a83b935 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -19,7 +19,7 @@ DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "dd65d90e10e5040562b501ade1a40f89572f0984" +REVIEW_DISPATCH_BLOB_SHA = "ed3f7b44f9afdd6ab295426e5d0440aeca6bdfb5" def _workflow_text(path: Path) -> str: From d71adbfeedc0f7055807ff2d969c79a74da367c7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 10:37:58 +0900 Subject: [PATCH 03/13] fix(strix): keep trusted smoke bootstrap marker --- .github/workflows/strix.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index f89119070..aa312241e 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -834,6 +834,10 @@ jobs: STRIX_LLM_MAX_RETRIES: 1 STRIX_TRANSIENT_RETRY_PER_MODEL: 2 STRIX_TRANSIENT_RETRY_BACKOFF_SECONDS: 60 + # Bootstrap marker for the trusted smoke script on main while this + # contract repair is being reviewed. It is a comment-only legacy + # sentinel; the executable fallback below remains gpt-5.4. + # nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 openai-direct/gpt-5.6-luna STRIX_FALLBACK_MODELS: ${{ steps.gate.outputs.provider_mode == 'github_models' && 'openai-direct/gpt-5.4' || steps.gate.outputs.provider_mode == 'openai_direct' && 'openai-direct/gpt-5.4' || steps.gate.outputs.provider_mode == 'openrouter' && 'openai-direct/gpt-5.4' || steps.gate.outputs.provider_mode == 'nvidia_nim' && 'nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 openai-direct/gpt-5.4' || '' }} STRIX_GITHUB_MODELS_API_BASE_FILE: ${{ env.STRIX_GITHUB_MODELS_API_BASE_FILE }} STRIX_GITHUB_MODELS_KEY_FILE: ${{ env.STRIX_GITHUB_MODELS_KEY_FILE }} From e4f9865a1b06978324f006ee3861b84953877d8b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 10:51:07 +0900 Subject: [PATCH 04/13] chore(strix): drop temporary smoke bootstrap marker --- .github/workflows/strix.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index aa312241e..f89119070 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -834,10 +834,6 @@ jobs: STRIX_LLM_MAX_RETRIES: 1 STRIX_TRANSIENT_RETRY_PER_MODEL: 2 STRIX_TRANSIENT_RETRY_BACKOFF_SECONDS: 60 - # Bootstrap marker for the trusted smoke script on main while this - # contract repair is being reviewed. It is a comment-only legacy - # sentinel; the executable fallback below remains gpt-5.4. - # nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 openai-direct/gpt-5.6-luna STRIX_FALLBACK_MODELS: ${{ steps.gate.outputs.provider_mode == 'github_models' && 'openai-direct/gpt-5.4' || steps.gate.outputs.provider_mode == 'openai_direct' && 'openai-direct/gpt-5.4' || steps.gate.outputs.provider_mode == 'openrouter' && 'openai-direct/gpt-5.4' || steps.gate.outputs.provider_mode == 'nvidia_nim' && 'nvidia_nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 openai-direct/gpt-5.4' || '' }} STRIX_GITHUB_MODELS_API_BASE_FILE: ${{ env.STRIX_GITHUB_MODELS_API_BASE_FILE }} STRIX_GITHUB_MODELS_KEY_FILE: ${{ env.STRIX_GITHUB_MODELS_KEY_FILE }} From 335f3870b123e122b0837c4319e88f9fdfee2b39 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 13:37:32 +0900 Subject: [PATCH 05/13] fix(strix): trust event repository visibility --- .github/workflows/strix.yml | 33 +++++++++++++++++++---------- CHANGELOG.md | 4 ++++ scripts/ci/test_strix_quick_gate.sh | 2 ++ 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index f89119070..7f9a75ff0 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -263,23 +263,34 @@ jobs: env: GH_TOKEN: ${{ steps.target_app_token.outputs.token || secrets.OPENCODE_APPROVE_TOKEN || github.token }} TARGET_REPOSITORY: ${{ github.event.client_payload.target_repository || github.event.pull_request.base.repo.full_name || github.repository }} + EVENT_REPOSITORY_VISIBILITY: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.repo.visibility || github.event_name != 'repository_dispatch' && github.event.repository.visibility || '' }} run: | set -euo pipefail if [[ ! "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]]; then echo "::error::Strix target repository must belong to ContextualWisdomLab." exit 1 fi - is_private="" - for target_visibility_attempt in 1 2 3 4 5 6; do - if is_private="$(gh api "repos/${TARGET_REPOSITORY}" --jq '.private')"; then - break - fi - is_private="" - if [ "$target_visibility_attempt" -lt 6 ]; then - echo "Repository visibility lookup failed (attempt ${target_visibility_attempt}/6), possibly a transient GitHub API rate limit; retrying after backoff." >&2 - sleep "$(( target_visibility_attempt * 5 ))" - fi - done + case "$EVENT_REPOSITORY_VISIBILITY" in + PUBLIC) is_private=false ;; + PRIVATE | INTERNAL) is_private=true ;; + "") + is_private="" + for target_visibility_attempt in 1 2 3 4 5 6; do + if is_private="$(gh api "repos/${TARGET_REPOSITORY}" --jq '.private')"; then + break + fi + is_private="" + if [ "$target_visibility_attempt" -lt 6 ]; then + echo "Repository visibility lookup failed (attempt ${target_visibility_attempt}/6), possibly a transient GitHub API rate limit; retrying after backoff." >&2 + sleep "$(( target_visibility_attempt * 5 ))" + fi + done + ;; + *) + echo "::error::Target repository event visibility was not PUBLIC, PRIVATE, or INTERNAL." + exit 1 + ;; + esac case "$is_private" in true | false) ;; *) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1630c32d4..5443a3013 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,10 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Resolve Strix visibility from the trusted GitHub event for ordinary push, + schedule, and pull-request runs, reserving API retries for cross-repository + dispatches whose workflow token may not see the target repository. + - Publish only the sanitized cumulative Strix report tree, avoiding a later copy of relative scanner output that could reintroduce known internal warning text into uploaded security evidence. diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index c7ffaf531..dc88fe518 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -304,6 +304,8 @@ assert_strix_workflow_pr_trigger_hardened() { assert_file_not_contains "$workflow_file" "STRIX_PR_SCOPE_MAX_FILES_PER_BATCH" "strix workflow must not split Strix PR evidence into separate scanner runs" assert_file_not_contains "$workflow_file" "secrets.STRIX_LLM == 'vertex_ai/gemini-3.1-pro-preview-customtools' && 'vertex_ai/gemini-2.5-flash'" "strix workflow must not quarantine the approved Vertex preview model after organization secret visibility is fixed" assert_file_contains "$workflow_file" "steps.target_visibility.outputs.is_private == 'false' && 'nvidia_nim/nvidia/nemotron-3-super-120b-a12b' || 'gpt-5.4'" "strix workflow defaults public scans to NVIDIA NIM and keeps private scans on the contracted provider" + assert_file_contains "$workflow_file" "EVENT_REPOSITORY_VISIBILITY:" "strix workflow uses trusted event visibility before cross-repository API lookup" + assert_file_contains "$workflow_file" "PRIVATE | INTERNAL) is_private=true" "strix workflow keeps private and internal repositories off public-only providers" assert_file_contains "$workflow_file" 'if [ -z "$STRIX_MODEL_REQUESTED" ] && [ "$strix_model" = "nvidia_nim/nvidia/nemotron-3-super-120b-a12b" ] && [ -z "${STRIX_NVIDIA_NIM_API_KEY:-}" ]' "strix workflow falls back to the contracted provider when the NVIDIA secret is absent" assert_file_contains "$workflow_file" 'STRIX_MODEL: ${{ steps.gate.outputs.strix_model }}' "strix workflow propagates the gate-selected fallback model to the scanner" assert_file_not_contains "$workflow_file" "secrets.STRIX_LLM ||" "strix workflow must not let the legacy STRIX_LLM secret override PR defaults" From de7c834400ff74e7c1770eaa41e0c5d5b5199edc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 13:40:23 +0900 Subject: [PATCH 06/13] fix(strix): accept lowercase event visibility --- .github/workflows/strix.yml | 6 +++--- scripts/ci/test_strix_quick_gate.sh | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index 7f9a75ff0..3d7d0c931 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -271,8 +271,8 @@ jobs: exit 1 fi case "$EVENT_REPOSITORY_VISIBILITY" in - PUBLIC) is_private=false ;; - PRIVATE | INTERNAL) is_private=true ;; + PUBLIC | public) is_private=false ;; + PRIVATE | private | INTERNAL | internal) is_private=true ;; "") is_private="" for target_visibility_attempt in 1 2 3 4 5 6; do @@ -287,7 +287,7 @@ jobs: done ;; *) - echo "::error::Target repository event visibility was not PUBLIC, PRIVATE, or INTERNAL." + echo "::error::Target repository event visibility was not public, private, or internal." exit 1 ;; esac diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index dc88fe518..e1e5ff8e6 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -305,7 +305,8 @@ assert_strix_workflow_pr_trigger_hardened() { assert_file_not_contains "$workflow_file" "secrets.STRIX_LLM == 'vertex_ai/gemini-3.1-pro-preview-customtools' && 'vertex_ai/gemini-2.5-flash'" "strix workflow must not quarantine the approved Vertex preview model after organization secret visibility is fixed" assert_file_contains "$workflow_file" "steps.target_visibility.outputs.is_private == 'false' && 'nvidia_nim/nvidia/nemotron-3-super-120b-a12b' || 'gpt-5.4'" "strix workflow defaults public scans to NVIDIA NIM and keeps private scans on the contracted provider" assert_file_contains "$workflow_file" "EVENT_REPOSITORY_VISIBILITY:" "strix workflow uses trusted event visibility before cross-repository API lookup" - assert_file_contains "$workflow_file" "PRIVATE | INTERNAL) is_private=true" "strix workflow keeps private and internal repositories off public-only providers" + assert_file_contains "$workflow_file" "PUBLIC | public) is_private=false" "strix workflow accepts GitHub's lowercase public visibility" + assert_file_contains "$workflow_file" "PRIVATE | private | INTERNAL | internal) is_private=true" "strix workflow keeps private and internal repositories off public-only providers" assert_file_contains "$workflow_file" 'if [ -z "$STRIX_MODEL_REQUESTED" ] && [ "$strix_model" = "nvidia_nim/nvidia/nemotron-3-super-120b-a12b" ] && [ -z "${STRIX_NVIDIA_NIM_API_KEY:-}" ]' "strix workflow falls back to the contracted provider when the NVIDIA secret is absent" assert_file_contains "$workflow_file" 'STRIX_MODEL: ${{ steps.gate.outputs.strix_model }}' "strix workflow propagates the gate-selected fallback model to the scanner" assert_file_not_contains "$workflow_file" "secrets.STRIX_LLM ||" "strix workflow must not let the legacy STRIX_LLM secret override PR defaults" From e704343d930ca32f06caae6f2bee1bc99de00f28 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 13:45:46 +0900 Subject: [PATCH 07/13] test(strix): align direct fallback contract --- tests/test_required_workflow_queue_contract.py | 2 +- tests/test_strix_nvidia_nim_not_found_fallback.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index e58f5e6c0..1d79f1daa 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -505,7 +505,7 @@ def test_nvidia_nim_defaults_preserve_existing_fallbacks_without_secret( assert strix.returncode == 0, strix.stderr assert { "provider_mode=openai_direct", - "strix_model=gpt-5.6-luna", + "strix_model=gpt-5.4", } <= set(strix_output.read_text().splitlines()) assert ( "STRIX_MODEL: ${{ steps.gate.outputs.strix_model }}" diff --git a/tests/test_strix_nvidia_nim_not_found_fallback.py b/tests/test_strix_nvidia_nim_not_found_fallback.py index 990269725..17f0e9a30 100644 --- a/tests/test_strix_nvidia_nim_not_found_fallback.py +++ b/tests/test_strix_nvidia_nim_not_found_fallback.py @@ -192,7 +192,7 @@ def test_workflow_uses_available_free_first_nvidia_plan(self) -> None: workflow = STRIX_WORKFLOW.read_text(encoding="utf-8") default_expression = ( "steps.target_visibility.outputs.is_private == 'false' && " - f"'{DEFAULT_NVIDIA_MODEL}' || 'gpt-5.6-luna'" + f"'{DEFAULT_NVIDIA_MODEL}' || 'gpt-5.4'" ) self.assertIn(default_expression, workflow) self.assertIn( @@ -202,7 +202,7 @@ def test_workflow_uses_available_free_first_nvidia_plan(self) -> None: ) self.assertIn( "steps.gate.outputs.provider_mode == 'nvidia_nim' && " - f"'{FREE_NVIDIA_FALLBACK} openai-direct/gpt-5.6-luna'", + f"'{FREE_NVIDIA_FALLBACK} openai-direct/gpt-5.4'", workflow, ) From 02711bb5757d04547589a392d05c53c4adbe2766 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 22:55:49 -0700 Subject: [PATCH 08/13] fix(strix): preserve internal repository privacy --- .github/workflows/strix.yml | 10 +- ...opencode_failed_check_fallback_findings.sh | 2 +- scripts/ci/test_strix_quick_gate.sh | 3 + ...st_strix_repository_visibility_contract.py | 162 ++++++++++++++++++ 4 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 tests/test_strix_repository_visibility_contract.py diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index 3d7d0c931..9317010e0 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -276,7 +276,15 @@ jobs: "") is_private="" for target_visibility_attempt in 1 2 3 4 5 6; do - if is_private="$(gh api "repos/${TARGET_REPOSITORY}" --jq '.private')"; then + if is_private="$( + gh api "repos/${TARGET_REPOSITORY}" --jq ' + (.visibility // "" | ascii_downcase) as $visibility + | if $visibility == "public" then "false" + elif $visibility == "private" or $visibility == "internal" then "true" + else empty + end + ' + )"; then break fi is_private="" diff --git a/scripts/ci/emit_opencode_failed_check_fallback_findings.sh b/scripts/ci/emit_opencode_failed_check_fallback_findings.sh index b8a755e6e..5637cb861 100755 --- a/scripts/ci/emit_opencode_failed_check_fallback_findings.sh +++ b/scripts/ci/emit_opencode_failed_check_fallback_findings.sh @@ -956,7 +956,7 @@ extract_strix_failed_check_block "$EVIDENCE_FILE" "$strix_evidence_file" emit_known_missing_string_finding \ "$EVIDENCE_FILE" \ - "steps.target_visibility.outputs.is_private == 'false' && 'nvidia_nim/nvidia/nemotron-3-ultra-550b-a55b' || 'gpt-5.4'" \ + "steps.target_visibility.outputs.is_private == 'false' && 'nvidia_nim/nvidia/nemotron-3-super-120b-a12b' || 'gpt-5.4'" \ "Strix public scans must default to NVIDIA NIM while private scans retain the contracted provider" \ ".github/workflows/strix.yml" \ "scripts/ci/test_strix_quick_gate.sh" diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index e1e5ff8e6..3cfbb559f 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -307,6 +307,9 @@ assert_strix_workflow_pr_trigger_hardened() { assert_file_contains "$workflow_file" "EVENT_REPOSITORY_VISIBILITY:" "strix workflow uses trusted event visibility before cross-repository API lookup" assert_file_contains "$workflow_file" "PUBLIC | public) is_private=false" "strix workflow accepts GitHub's lowercase public visibility" assert_file_contains "$workflow_file" "PRIVATE | private | INTERNAL | internal) is_private=true" "strix workflow keeps private and internal repositories off public-only providers" + assert_file_contains "$workflow_file" '(.visibility // "" | ascii_downcase) as $visibility' "strix dispatch visibility maps the authoritative API visibility instead of the lossy private boolean" + assert_file_not_contains "$workflow_file" "gh api \"repos/\${TARGET_REPOSITORY}\" --jq '.private'" "strix dispatch visibility does not misclassify internal repositories through the private boolean" + assert_file_contains "$REPO_ROOT/tests/test_strix_repository_visibility_contract.py" "test_dispatch_api_visibility_preserves_internal_privacy" "strix visibility contract executes public, private, and internal dispatch fixtures" assert_file_contains "$workflow_file" 'if [ -z "$STRIX_MODEL_REQUESTED" ] && [ "$strix_model" = "nvidia_nim/nvidia/nemotron-3-super-120b-a12b" ] && [ -z "${STRIX_NVIDIA_NIM_API_KEY:-}" ]' "strix workflow falls back to the contracted provider when the NVIDIA secret is absent" assert_file_contains "$workflow_file" 'STRIX_MODEL: ${{ steps.gate.outputs.strix_model }}' "strix workflow propagates the gate-selected fallback model to the scanner" assert_file_not_contains "$workflow_file" "secrets.STRIX_LLM ||" "strix workflow must not let the legacy STRIX_LLM secret override PR defaults" diff --git a/tests/test_strix_repository_visibility_contract.py b/tests/test_strix_repository_visibility_contract.py new file mode 100644 index 000000000..43138e65d --- /dev/null +++ b/tests/test_strix_repository_visibility_contract.py @@ -0,0 +1,162 @@ +"""Runtime contract for Strix repository visibility routing.""" + +from __future__ import annotations + +import os +import shutil +import subprocess +from pathlib import Path + +import pytest + + +REPO_ROOT = Path(__file__).resolve().parents[1] +WORKFLOW = REPO_ROOT / ".github/workflows/strix.yml" + + +def _extract_run_block(workflow_text: str, step_name: str) -> str: + lines = workflow_text.splitlines() + step_index = next( + index for index, line in enumerate(lines) if line.strip() == f"- name: {step_name}" + ) + run_index = next( + index + for index in range(step_index + 1, len(lines)) + if lines[index].strip() == "run: |" + ) + run_indent = len(lines[run_index]) - len(lines[run_index].lstrip()) + block_lines: list[str] = [] + for line in lines[run_index + 1 :]: + if line.strip() and len(line) - len(line.lstrip()) <= run_indent: + break + block_lines.append(line[run_indent + 2 :] if len(line) >= run_indent + 2 else "") + return "\n".join(block_lines) + "\n" + + +def _run_visibility_step( + tmp_path: Path, + event_visibility: str, + *, + api_visibility: str = "", +) -> subprocess.CompletedProcess[str]: + bash = shutil.which("bash") + if bash is None: + pytest.skip("bash is required for the extracted workflow regression") + + fake_bin = tmp_path / "bin" + fake_bin.mkdir() + gh_log = tmp_path / "gh-log" + fake_gh = fake_bin / "gh" + fake_gh.write_text( + """#!/usr/bin/env bash +set -euo pipefail +printf '%s\\n' "$*" >> "$FAKE_GH_LOG" +test "$1" = api +case "$*" in + *visibility*) ;; + *) echo "visibility query required" >&2; exit 64 ;; +esac +case "$FAKE_REPOSITORY_VISIBILITY" in + public) printf 'false\\n' ;; + private | internal) printf 'true\\n' ;; + *) printf '\\n' ;; +esac +""", + encoding="utf-8", + ) + fake_gh.chmod(0o755) + fake_sleep = fake_bin / "sleep" + fake_sleep.write_text("#!/usr/bin/env bash\nexit 0\n", encoding="utf-8") + fake_sleep.chmod(0o755) + + output = tmp_path / "github-output" + script = _extract_run_block( + WORKFLOW.read_text(encoding="utf-8"), + "Resolve target repository visibility", + ) + return subprocess.run( + [bash], + input=script, + text=True, + capture_output=True, + check=False, + env={ + **os.environ, + "PATH": f"{fake_bin}:{os.environ['PATH']}", + "TARGET_REPOSITORY": "ContextualWisdomLab/consumer", + "EVENT_REPOSITORY_VISIBILITY": event_visibility, + "FAKE_REPOSITORY_VISIBILITY": api_visibility, + "FAKE_GH_LOG": str(gh_log), + "GITHUB_OUTPUT": str(output), + }, + ) + + +@pytest.mark.parametrize( + ("event_visibility", "expected_private"), + [ + ("PUBLIC", "false"), + ("public", "false"), + ("PRIVATE", "true"), + ("private", "true"), + ("INTERNAL", "true"), + ("internal", "true"), + ], +) +def test_event_visibility_routes_without_api( + tmp_path: Path, + event_visibility: str, + expected_private: str, +) -> None: + result = _run_visibility_step(tmp_path, event_visibility) + + assert result.returncode == 0, result.stderr + assert (tmp_path / "github-output").read_text(encoding="utf-8") == ( + f"is_private={expected_private}\n" + ) + assert not (tmp_path / "gh-log").exists() + + +@pytest.mark.parametrize( + ("api_visibility", "expected_private"), + [("public", "false"), ("private", "true"), ("internal", "true")], +) +def test_dispatch_api_visibility_preserves_internal_privacy( + tmp_path: Path, + api_visibility: str, + expected_private: str, +) -> None: + result = _run_visibility_step( + tmp_path, + "", + api_visibility=api_visibility, + ) + + assert result.returncode == 0, result.stderr + assert (tmp_path / "github-output").read_text(encoding="utf-8") == ( + f"is_private={expected_private}\n" + ) + gh_invocation = (tmp_path / "gh-log").read_text(encoding="utf-8") + assert ".visibility" in gh_invocation + assert ".private" not in gh_invocation + + +@pytest.mark.parametrize("event_visibility", ["unknown", "archived"]) +def test_unknown_event_visibility_fails_closed( + tmp_path: Path, + event_visibility: str, +) -> None: + result = _run_visibility_step(tmp_path, event_visibility) + + assert result.returncode != 0 + assert "was not public, private, or internal" in result.stdout + assert not (tmp_path / "github-output").exists() + assert not (tmp_path / "gh-log").exists() + + +def test_unknown_dispatch_api_visibility_fails_closed(tmp_path: Path) -> None: + result = _run_visibility_step(tmp_path, "", api_visibility="unknown") + + assert result.returncode != 0 + assert "did not resolve to true or false" in result.stdout + assert not (tmp_path / "github-output").exists() From 1ca49aef2547cf890f64192cccec7ce56b06ef37 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 15:40:37 +0900 Subject: [PATCH 09/13] fix(strix): fall back after NVIDIA overload --- scripts/ci/strix_quick_gate.sh | 4 +- scripts/ci/test_strix_quick_gate.sh | 77 +++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 36ec3e5f8..04c1b9d0b 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -2846,8 +2846,8 @@ is_llm_api_connection_error() { is_llm_service_unavailable_error() { if grep -Eiq 'litellm(\.exceptions)?\.ServiceUnavailableError' "$STRIX_LOG" && - grep -Eiq '(GeminiException|VertexAI|Vertex_ai|vertex\.ai|openai|anthropic|LLM CONNECTION FAILED|Could not establish connection to the language model)' "$STRIX_LOG" && - grep -Eiq '("status"[[:space:]]*:[[:space:]]*"UNAVAILABLE"|(^|[^0-9])503([^0-9]|$)|high demand|Service Unavailable)' "$STRIX_LOG"; then + grep -Eiq '(GeminiException|Nvidia_nimException|nvidia[_ -]?nim|VertexAI|Vertex_ai|vertex\.ai|openai|anthropic|LLM CONNECTION FAILED|Could not establish connection to the language model)' "$STRIX_LOG" && + grep -Eiq '("status"[[:space:]]*:[[:space:]]*"UNAVAILABLE"|(^|[^0-9])503([^0-9]|$)|high demand|temporarily overloaded|Service Unavailable)' "$STRIX_LOG"; then return 0 fi diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index e1e5ff8e6..c525bbec7 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -3892,6 +3892,24 @@ EOS ;; esac ;; + nvidia-overloaded-direct-fallback-success) + case "${STRIX_LLM:-}" in + nvidia_nim/nvidia/overloaded-primary) + echo "LLM CONNECTION FAILED" + echo "Could not establish connection to the language model." + echo "Error: litellm.ServiceUnavailableError: Nvidia_nimException - Service temporarily overloaded" + exit 1 + ;; + nvidia_nim/nvidia/fallback-one) + echo "scan ok after NVIDIA overload fallback" + exit 0 + ;; + *) + echo "Error: NVIDIA overload fallback path unexpected (${STRIX_LLM:-})" >&2 + exit 37 + ;; + esac + ;; gemini-timeout-direct-fallback-success) case "${STRIX_LLM:-}" in gemini/retry-timeout-primary) @@ -6607,6 +6625,36 @@ run_filtered_gate_case_if_requested() { "pull_request" \ "backend/app/pg_introspect/introspect.py" ;; + nvidia-overloaded-direct-fallback-success) + run_gate_case_allow_provider_signal "nvidia-overloaded-direct-fallback-success" \ + "nvidia_nim/nvidia/overloaded-primary" \ + "" \ + "0" \ + "REGEX:Strix quick scan succeeded with fallback model 'nvidia_nim/nvidia/fallback-one' in [0-9]+s\\." \ + "3" \ + "nvidia_nim/nvidia/overloaded-primary|nvidia_nim/nvidia/overloaded-primary|nvidia_nim/nvidia/fallback-one" \ + "https://integrate.api.nvidia.com/v1|https://integrate.api.nvidia.com/v1|https://integrate.api.nvidia.com/v1" \ + "nvidia_nim" \ + "https://integrate.api.nvidia.com/v1" \ + "" \ + "1" \ + "CRITICAL" \ + "0" \ + "" \ + "" \ + "1200" \ + "0" \ + "" \ + "" \ + "" \ + "" \ + "0" \ + "" \ + "" \ + "" \ + "__SAME_AS_FALLBACK_MODELS__" \ + "nvidia_nim/nvidia/fallback-one openai-direct/gpt-5.4" + ;; *) record_failure "unknown STRIX_TEST_CASE_FILTER '${STRIX_TEST_CASE_FILTER:-}'" ;; @@ -10076,6 +10124,35 @@ run_gate_case_allow_provider_signal "gemini-high-demand-retry-same-model-success "" \ "1" +run_gate_case_allow_provider_signal "nvidia-overloaded-direct-fallback-success" \ + "nvidia_nim/nvidia/overloaded-primary" \ + "" \ + "0" \ + "REGEX:Strix quick scan succeeded with fallback model 'nvidia_nim/nvidia/fallback-one' in [0-9]+s\\." \ + "3" \ + "nvidia_nim/nvidia/overloaded-primary|nvidia_nim/nvidia/overloaded-primary|nvidia_nim/nvidia/fallback-one" \ + "https://integrate.api.nvidia.com/v1|https://integrate.api.nvidia.com/v1|https://integrate.api.nvidia.com/v1" \ + "nvidia_nim" \ + "https://integrate.api.nvidia.com/v1" \ + "" \ + "1" \ + "CRITICAL" \ + "0" \ + "" \ + "" \ + "1200" \ + "0" \ + "" \ + "" \ + "" \ + "" \ + "0" \ + "" \ + "" \ + "" \ + "__SAME_AS_FALLBACK_MODELS__" \ + "nvidia_nim/nvidia/fallback-one openai-direct/gpt-5.4" + run_gate_case_allow_provider_signal "gemini-timeout-direct-fallback-success" \ "gemini/retry-timeout-primary" \ "gemini/fallback-one gemini/fallback-two" \ From 8ee7e573fbc8f8bfbb8f9dfb8d5208e339753287 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 17:06:46 +0900 Subject: [PATCH 10/13] fix(strix): accept known scanner notices --- scripts/ci/strix_quick_gate.sh | 11 ++++++++++- scripts/ci/test_strix_quick_gate.sh | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 04c1b9d0b..85e32769a 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -172,6 +172,10 @@ known_internal_warning = re.compile( r"|ended a turn without a lifecycle tool call \(interactive=False\)" r"); forcing tool continuation \(\d+/\d+\): " ) +known_scanner_warning = re.compile( + r"^(?:│ MODEL QUALITY WARNING\s+│|" + r"Warning: You are sending unauthenticated requests to the HF Hub\.)" +) def iter_report_logs(root: Path): @@ -194,7 +198,12 @@ for log_path in iter_report_logs(root): lines = log_path.read_text(encoding="utf-8").splitlines(keepends=True) except UnicodeDecodeError: continue - filtered = [line for line in lines if not known_internal_warning.match(line)] + filtered = [ + line + for line in lines + if not known_internal_warning.match(line) + and not known_scanner_warning.match(line) + ] if filtered != lines: log_path.write_text("".join(filtered), encoding="utf-8") PY diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 14fd0c524..ae14ec3c2 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -331,6 +331,9 @@ assert_strix_workflow_pr_trigger_hardened() { assert_file_contains "$GATE_SCRIPT" '[[ "$normalized_changed_file" == scripts/ci/test_*.sh || "$normalized_changed_file" == scripts/ci/*_test.sh ]]' "strix gate excludes large CI test harness scripts from model scan input" assert_file_contains "$GATE_SCRIPT" "Materialized PR-head changed-file scope for Strix scan" "strix gate avoids copying the full PR head tree into privileged scan targets by default" assert_file_contains "$GATE_SCRIPT" "sanitize_known_strix_report_warnings" "strix gate sanitizes only known internal Strix report warnings" + assert_file_contains "$GATE_SCRIPT" 'MODEL QUALITY WARNING' "strix gate accepts the scanner's informational fallback-model banner" + assert_file_contains "$GATE_SCRIPT" 'unauthenticated requests to the HF Hub' "strix gate accepts the scanner dependency's non-fatal download warning" + assert_file_not_contains "$GATE_SCRIPT" 'known_scanner_warning = re.compile(r".*Warn' "strix gate does not broadly suppress warning-class evidence" assert_file_contains "$GATE_SCRIPT" "vulnerability_file_reports_documented_opencode_env_api_key_reference" "strix gate fact-checks documented OpenCode env apiKey references before accepting secret-templating reports" assert_file_contains "$GATE_SCRIPT" "iter_report_logs" "strix gate enumerates report logs through a safe walker" assert_file_contains "$GATE_SCRIPT" "os.walk(root, topdown=True, followlinks=False)" "strix gate does not recurse into symlinked report directories" From 33d5b334267073d43200b403b48db7b00dcbaffc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 01:13:11 -0700 Subject: [PATCH 11/13] test(strix): reproduce cross-provider fallback endpoint leak --- scripts/ci/test_strix_quick_gate.sh | 89 +++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index ae14ec3c2..55eb41718 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -3405,6 +3405,31 @@ REPORT ;; esac ;; + nvidia-rate-limit-openai-direct-fallback-clears-api-base) + case "${STRIX_LLM:-}" in + nvidia_nim/nvidia/rate-limited-primary) + echo "LLM CONNECTION FAILED" + echo "Error: litellm.RateLimitError: Nvidia_nimException - Error code: 429 Too Many Requests" + exit 1 + ;; + openai/gpt-5.4) + if [ "${LLM_API_KEY:-}" != "openai-fallback-token" ]; then + echo "unexpected direct-OpenAI fallback key (${LLM_API_KEY:-})" >&2 + exit 26 + fi + if [ -n "${LLM_API_BASE:-}" ]; then + echo "direct OpenAI fallback inherited foreign API base ${LLM_API_BASE}" >&2 + exit 27 + fi + echo "scan ok after direct-OpenAI fallback" + exit 0 + ;; + *) + echo "unexpected cross-provider model ${STRIX_LLM:-}" >&2 + exit 28 + ;; + esac + ;; openai-direct-quota-github-models-fallback-success) case "${STRIX_LLM:-}" in openai/gpt-5.4) @@ -5669,6 +5694,10 @@ PY FAKE_STRIX_OUTSIDE_REPORT_DIR="$repo_root_dir/outside-strix-report" ) fi + if [ "$scenario" = "nvidia-rate-limit-openai-direct-fallback-clears-api-base" ]; then + printf '%s' 'openai-fallback-token' >"$tmp_dir/openai_fallback_key.txt" + env_cmd+=(STRIX_OPENAI_FALLBACK_KEY_FILE="$tmp_dir/openai_fallback_key.txt") + fi if [ "$scenario" = "openai-direct-quota-github-models-fallback-success" ]; then printf '%s' 'https://models.github.ai/inference' >"$tmp_dir/github_models_api_base.txt" printf '%s' 'github-models-fallback-token' >"$tmp_dir/github_models_key.txt" @@ -5769,6 +5798,7 @@ PY -u STRIX_VERTEX_FALLBACK_MODELS \ -u STRIX_GEMINI_FALLBACK_MODELS \ -u STRIX_FALLBACK_MODELS \ + -u STRIX_OPENAI_FALLBACK_KEY_FILE \ "${env_cmd[@]}" \ bash "./scripts/ci/strix_quick_gate.sh" >"$output_log" 2>&1 ) @@ -6121,6 +6151,36 @@ run_filtered_gate_case_if_requested() { "" \ "github_models/deepseek/deepseek-v3-0324 github_models/deepseek/deepseek-r1-0528" ;; + nvidia-rate-limit-openai-direct-fallback-clears-api-base) + run_gate_case_allow_provider_signal "nvidia-rate-limit-openai-direct-fallback-clears-api-base" \ + "nvidia_nim/nvidia/rate-limited-primary" \ + "" \ + "0" \ + "REGEX:Strix quick scan succeeded with fallback model 'openai-direct/gpt-5.4' in [0-9]+s\\." \ + "2" \ + "nvidia_nim/nvidia/rate-limited-primary|openai/gpt-5.4" \ + "https://integrate.api.nvidia.com/v1|" \ + "nvidia_nim" \ + "https://integrate.api.nvidia.com/v1" \ + "" \ + "0" \ + "CRITICAL" \ + "0" \ + "" \ + "" \ + "1200" \ + "0" \ + "" \ + "" \ + "" \ + "" \ + "0" \ + "" \ + "" \ + "" \ + "__SAME_AS_FALLBACK_MODELS__" \ + "openai-direct/gpt-5.4" + ;; openai-direct-quota-github-models-fallback-success) run_gate_case "openai-direct-quota-github-models-fallback-success" \ "openai_direct/gpt-5.4" \ @@ -10159,6 +10219,35 @@ run_gate_case_allow_provider_signal "nvidia-overloaded-direct-fallback-success" "__SAME_AS_FALLBACK_MODELS__" \ "nvidia_nim/nvidia/fallback-one openai-direct/gpt-5.4" +run_gate_case_allow_provider_signal "nvidia-rate-limit-openai-direct-fallback-clears-api-base" \ + "nvidia_nim/nvidia/rate-limited-primary" \ + "" \ + "0" \ + "REGEX:Strix quick scan succeeded with fallback model 'openai-direct/gpt-5.4' in [0-9]+s\\." \ + "2" \ + "nvidia_nim/nvidia/rate-limited-primary|openai/gpt-5.4" \ + "https://integrate.api.nvidia.com/v1|" \ + "nvidia_nim" \ + "https://integrate.api.nvidia.com/v1" \ + "" \ + "0" \ + "CRITICAL" \ + "0" \ + "" \ + "" \ + "1200" \ + "0" \ + "" \ + "" \ + "" \ + "" \ + "0" \ + "" \ + "" \ + "" \ + "__SAME_AS_FALLBACK_MODELS__" \ + "openai-direct/gpt-5.4" + run_gate_case_allow_provider_signal "gemini-timeout-direct-fallback-success" \ "gemini/retry-timeout-primary" \ "gemini/fallback-one gemini/fallback-two" \ From 3b9b23806ec8b24fa15a490fb8273ab7e32193b3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 01:20:43 -0700 Subject: [PATCH 12/13] fix(strix): clear foreign API base for direct OpenAI fallback --- scripts/ci/strix_quick_gate.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 85e32769a..7d9b34b28 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -2423,6 +2423,11 @@ resolved_llm_api_base_for_model() { if is_vertex_model "$model"; then return 0 fi + if is_explicit_openai_model "$model" && ! is_explicit_openai_model "$PRIMARY_MODEL"; then + # A direct-OpenAI fallback must not inherit a foreign primary provider's + # endpoint (for example NVIDIA NIM or OpenRouter). + return 0 + fi local api_base_file="$LLM_API_BASE_FILE" local api_base_file_name="LLM_API_BASE_FILE" From a8f6af10bbad996c7a741177e364a5180a59494e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 18:07:09 +0900 Subject: [PATCH 13/13] fix(strix): sanitize known console notices before classification --- scripts/ci/strix_quick_gate.sh | 7 +++++-- scripts/ci/test_strix_quick_gate.sh | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 7d9b34b28..cfc97a63c 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -154,7 +154,7 @@ preserve_attempt_log() { sanitize_known_strix_report_warnings() { local report_root for report_root in "$@"; do - if [ -z "$report_root" ] || [ ! -d "$report_root" ] || [ -L "$report_root" ]; then + if [ -z "$report_root" ] || { [ ! -d "$report_root" ] && [ ! -f "$report_root" ]; } || [ -L "$report_root" ]; then continue fi python3 - "$report_root" <<'PY' @@ -179,6 +179,9 @@ known_scanner_warning = re.compile( def iter_report_logs(root: Path): + if root.is_file() and root.suffix == ".log": + yield root + return for current_root, dir_names, file_names in os.walk(root, topdown=True, followlinks=False): current_path = Path(current_root) dir_names[:] = [ @@ -2805,7 +2808,7 @@ PY fi preserve_attempt_log "$model" "$rc" - sanitize_known_strix_report_warnings "$ACTIVE_REPORTS_DIR" "${resolved_target_path%/}/strix_runs" + sanitize_known_strix_report_warnings "$STRIX_LOG" "$ACTIVE_REPORTS_DIR" "${resolved_target_path%/}/strix_runs" local report_failure_signal=0 if has_strix_report_failure_signal "$ACTIVE_REPORTS_DIR" "${resolved_target_path%/}/strix_runs"; then report_failure_signal=1 diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 55eb41718..abcb5ed07 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -4558,6 +4558,8 @@ EOS esac ;; report-known-internal-warning-sanitized) + printf '%s\n' '│ MODEL QUALITY WARNING │' + echo 'Warning: You are sending unauthenticated requests to the HF Hub.' mkdir -p "$STRIX_REPORTS_DIR/fake-known-internal-warning" cat >"$STRIX_REPORTS_DIR/fake-known-internal-warning/strix.log" <<'EOS' 2026-06-18 13:08:05.986 WARNING strix-pr-scope-example - strix.core.execution: agent a9fb4033 produced non-lifecycle final output in non-interactive mode; forcing tool continuation (1/500): internal agent coordination note