diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 8a8f8b31..da4f19eb 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -206,7 +206,8 @@ has_strix_report_failure_signal() { continue fi while IFS= read -r -d '' report_log; do - if grep -Eiq '(^|[^[:alpha:]])(Fatal|Denied|Warn|Warning|WARNING|Timeout)([^[:alpha:]]|$)' "$report_log"; then + if grep -Eiq '(^|[^[:alpha:]])(Fatal|Denied|Warn|Warning|WARNING)([^[:alpha:]]|$)' "$report_log" || + grep -Eiv 'timeout[[:space:]]+disabled' "$report_log" | grep -Eiq '(^|[^[:alpha:]])Timeout([^[:alpha:]]|$)'; then return 0 fi done < <(find "$report_root" -type f -name '*.log' -print0) @@ -405,13 +406,13 @@ if "\\" in relative_path_str: normalized = posixpath.normpath(relative_path_str) if normalized in (".", "") or normalized.startswith("../") or normalized == "..": raise SystemExit(1) -# '@' is required for Apple/Tauri retina asset names (128x128@2x.png) and '+' -# for SvelteKit's mandatory route files (+page.svelte, +layout.ts). Preserve -# the existing ASCII allowlist and additionally accept only Unicode letters, -# combining marks, and numbers. This supports internationalized repository -# paths without admitting controls, separators, bidi formatting, shell -# metacharacters, or Unicode punctuation that could resemble a path boundary. -allowed_ascii = frozenset("_.@+/ []-") +# '@' is required for Apple/Tauri retina asset names (128x128@2x.png), '+' for +# SvelteKit's mandatory route files (+page.svelte, +layout.ts), and comma plus +# parentheses for legal package fixture paths. Preserve the bounded ASCII +# allowlist and additionally accept only Unicode letters, combining marks, and +# numbers. This supports legitimate repository paths without admitting +# controls, alternate separators, bidi formatting, or pathspec syntax. +allowed_ascii = frozenset("_.@+/ [],()-") if not all( (character.isascii() and (character.isalnum() or character in allowed_ascii)) or ( @@ -718,7 +719,7 @@ is_github_models_api_compatible_model() { is_github_models_api_base() { local api_base="$1" case "$api_base" in - https://models.github.ai | https://models.github.ai/*) + https://models.github.ai/inference | https://models.github.ai/inference/) return 0 ;; *) @@ -1945,6 +1946,9 @@ extract_max_severity_rank() { vulnerability_file_is_below_threshold() { local vuln_file="$1" local threshold_rank report_rank + if [ ! -f "$vuln_file" ] || [ -L "$vuln_file" ]; then + return 1 + fi threshold_rank="$(severity_rank "$STRIX_FAIL_ON_MIN_SEVERITY")" report_rank="$(extract_max_severity_rank "$vuln_file")" [ "$report_rank" -ge 0 ] && [ "$report_rank" -lt "$threshold_rank" ] @@ -2103,9 +2107,9 @@ evaluate_pull_request_findings() { fi if [ "$found_baseline_threshold_finding" -eq 1 ]; then - PR_FINDINGS_DECISION="allow_baseline" - echo "Strix findings are limited to unchanged files in this pull request; allowing pipeline continuation." >&2 - return 0 + PR_FINDINGS_DECISION="block_baseline_unproven" + echo "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." >&2 + return 1 fi return 1 @@ -2146,6 +2150,7 @@ fail_unmapped_threshold_report() { fi PR_FINDINGS_DECISION="block_unmapped" echo "Unable to map Strix findings to changed files; failing closed for pull request." >&2 + echo "Strix quick scan failed with a non-recoverable error." >&2 return 0 } @@ -2215,6 +2220,29 @@ has_distinct_fallback_model_for_model() { return 1 } +report_no_distinct_fallback_model() { + local model="$1" + local fallback_models_raw fallback_config_name + local fallback_models=() + local candidate_raw candidate + local configured_fallback_count=0 + + fallback_models_raw="$(fallback_models_raw_for_model "$model")" + fallback_models_raw="${fallback_models_raw//$'\r'/ }" + fallback_models_raw="${fallback_models_raw//$'\n'/ }" + read -r -a fallback_models <<<"$fallback_models_raw" + for candidate_raw in "${fallback_models[@]}"; do + candidate="$(normalize_model "$candidate_raw")" + [ -n "$candidate" ] && configured_fallback_count=$((configured_fallback_count + 1)) + done + fallback_config_name="$(fallback_models_config_name_for_model "$model")" + if [ "$configured_fallback_count" -eq 0 ]; then + echo "ERROR: No fallback models configured ($fallback_config_name is empty). Configure distinct models." >&2 + else + echo "ERROR: All configured fallback models are the same as the primary model" >&2 + fi +} + resolved_llm_api_base_for_model() { local model="$1" @@ -2250,6 +2278,10 @@ resolved_llm_api_base_for_model() { llm_api_base_value="${llm_api_base_value%%:generateContent*}" llm_api_base_value="$(trim_whitespace "$llm_api_base_value")" if [ -z "$llm_api_base_value" ]; then + if is_github_models_model "$model"; then + echo "ERROR: GitHub Models Strix scans require a non-empty LLM_API_BASE_FILE selecting the pinned GitHub Models inference endpoint." >&2 + return 2 + fi return 0 fi if [[ "$llm_api_base_value" =~ [[:space:][:cntrl:]] ]]; then @@ -2260,7 +2292,11 @@ resolved_llm_api_base_for_model() { echo "ERROR: LLM_API_BASE must be an https URL when configured." >&2 return 2 fi - if is_github_models_api_base "$llm_api_base_value" && ! is_github_models_api_compatible_model "$model"; then + if ! is_github_models_api_base "$llm_api_base_value"; then + echo "ERROR: LLM_API_BASE must use the pinned GitHub Models inference endpoint; arbitrary provider hosts are forbidden." >&2 + return 2 + fi + if ! is_github_models_api_compatible_model "$model"; then echo "ERROR: LLM_API_BASE may route through GitHub Models only when STRIX_LLM uses a GitHub Models-compatible model." >&2 return 2 fi @@ -2355,7 +2391,6 @@ run_strix_once() { STRIX_CHILD_EXECUTABLE_PATH="$STRIX_EXECUTABLE_PATH" \ STRIX_CHILD_EXECUTABLE_ROOT="$STRIX_EXECUTABLE_ROOT" \ STRIX_CHILD_EXECUTABLE_SHA256="$STRIX_EXECUTABLE_SHA256" \ - STRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \ python3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" <<'PY' import hashlib import hmac @@ -2377,7 +2412,6 @@ log_path.write_text("", encoding="utf-8") process_timeout = None if timeout_seconds == 0 else timeout_seconds child_env = {} for key in ( - "PATH", "HOME", "TMPDIR", "TMP", @@ -2397,6 +2431,7 @@ for key in ( value = os.environ.get(key) if value: child_env[key] = value +child_env["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" child_env["PYTHONWARNINGS"] = "ignore:Pydantic serializer warnings:UserWarning:pydantic.main" child_env["NPM_CONFIG_IGNORE_SCRIPTS"] = "true" child_env["npm_config_ignore_scripts"] = "true" @@ -2462,32 +2497,28 @@ if resolved_strix_path.stat().st_mode & (stat.S_IWGRP | stat.S_IWOTH): sys.stderr.write("ERROR: STRIX_EXECUTABLE_PATH must not be group/world writable.\n") raise SystemExit(127) -require_integrity = os.environ.get("STRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY", "").lower() in { - "1", "true", "yes", "on" -} -if require_integrity: - configured_root = os.environ.get("STRIX_CHILD_EXECUTABLE_ROOT", "") - configured_digest = os.environ.get("STRIX_CHILD_EXECUTABLE_SHA256", "").lower() - if not configured_root or len(configured_digest) != 64 or any( - char not in "0123456789abcdef" for char in configured_digest - ): - sys.stderr.write( - "ERROR: trusted PR evidence requires a pinned Strix executable root and SHA-256 digest.\n" - ) - raise SystemExit(127) - try: - resolved_root = pathlib.Path(configured_root).resolve(strict=True) - resolved_strix_path.relative_to(resolved_root) - except (OSError, ValueError): - sys.stderr.write("ERROR: STRIX_EXECUTABLE_PATH must be inside the pinned installation root.\n") - raise SystemExit(127) - if not resolved_root.is_dir() or resolved_root.stat().st_mode & (stat.S_IWGRP | stat.S_IWOTH): - sys.stderr.write("ERROR: the pinned Strix installation root must not be group/world writable.\n") - raise SystemExit(127) - actual_digest = hashlib.sha256(resolved_strix_path.read_bytes()).hexdigest() - if not hmac.compare_digest(actual_digest, configured_digest): - sys.stderr.write("ERROR: STRIX_EXECUTABLE_PATH did not match the pinned SHA-256 digest.\n") - raise SystemExit(127) +configured_root = os.environ.get("STRIX_CHILD_EXECUTABLE_ROOT", "") +configured_digest = os.environ.get("STRIX_CHILD_EXECUTABLE_SHA256", "").lower() +if not configured_root or len(configured_digest) != 64 or any( + char not in "0123456789abcdef" for char in configured_digest +): + sys.stderr.write( + "ERROR: every Strix invocation requires a pinned executable root and SHA-256 digest.\n" + ) + raise SystemExit(127) +try: + resolved_root = pathlib.Path(configured_root).resolve(strict=True) + resolved_strix_path.relative_to(resolved_root) +except (OSError, ValueError): + sys.stderr.write("ERROR: STRIX_EXECUTABLE_PATH must be inside the pinned installation root.\n") + raise SystemExit(127) +if not resolved_root.is_dir() or resolved_root.stat().st_mode & (stat.S_IWGRP | stat.S_IWOTH): + sys.stderr.write("ERROR: the pinned Strix installation root must not be group/world writable.\n") + raise SystemExit(127) +actual_digest = hashlib.sha256(resolved_strix_path.read_bytes()).hexdigest() +if not hmac.compare_digest(actual_digest, configured_digest): + sys.stderr.write("ERROR: STRIX_EXECUTABLE_PATH did not match the pinned SHA-256 digest.\n") + raise SystemExit(127) resolved_strix_bin = str(resolved_strix_path) try: @@ -2563,6 +2594,10 @@ PY fi fi preserve_attempt_log "$model" "$rc" + if [ "$rc" -eq 127 ]; then + printf "Strix executable integrity validation failed; treating this as a configuration failure.\n" >&2 + return 2 + fi sanitize_known_strix_report_warnings "$ACTIVE_REPORTS_DIR" "${resolved_target_path%/}/strix_runs" local report_failure_signal=0 @@ -2937,6 +2972,10 @@ has_detected_infrastructure_error() { return 0 fi + if is_vertex_not_found_error; then + return 0 + fi + # Generic strix non-zero exit with known transport/connection errors # that don't fall into the specific categories above. # Use LLM_PROVIDER_ONLY_REGEX (not PROVIDER_CONTEXT_REGEX) to avoid @@ -3037,6 +3076,11 @@ has_only_below_threshold_vulnerabilities() { done done + # The per-attempt log is also security evidence. A failed provider call can + # emit a higher-severity finding than a partial markdown report, so the + # threshold decision must consider both streams before allowing continuation. + update_max_severity_from_stream "$STRIX_LOG" + if [ "$found_any_vuln_file" -eq 0 ]; then echo "No Strix vulnerability report artifact was produced; log-only severity markers are incomplete evidence, so the scan is failing closed." >&2 return 1 @@ -3104,12 +3148,6 @@ has_blocking_vulnerability_reports() { } fail_reported_vulnerabilities_before_fallback_success() { - case "$PR_FINDINGS_DECISION" in - allow_baseline) - return 1 - ;; - esac - if has_blocking_vulnerability_reports; then echo "Strix model reported threshold vulnerabilities before fallback success; failing closed so every model-reported vulnerability is reviewed." >&2 echo "Strix quick scan failed with a non-recoverable error." >&2 @@ -3196,21 +3234,54 @@ vulnerability_file_has_absent_endpoint_finding() { local source_dirs_raw="${STRIX_SOURCE_DIRS:-.}" local resolved_target_root="" local resolved_dirs=() + local source_dir_entries=() local dir_entry if ! resolved_target_root="$(resolve_current_target_path "$TARGET_PATH" 2>/dev/null)"; then return 1 fi - # Disable globbing so that entries like "*" or "[" in STRIX_SOURCE_DIRS - # are not expanded by pathname expansion during word-splitting. - set -f - for dir_entry in $source_dirs_raw; do - local candidate="${resolved_target_root%/}/$dir_entry" - if [ -d "$candidate" ] && [ ! -L "$candidate" ]; then - resolved_dirs+=("$candidate") + if [[ "$source_dirs_raw" =~ [[:cntrl:]] ]]; then + echo "ERROR: STRIX_SOURCE_DIRS must not contain control characters." >&2 + return 1 + fi + read -r -a source_dir_entries <<<"$source_dirs_raw" + for dir_entry in "${source_dir_entries[@]}"; do + local resolved_source_dir + if ! resolved_source_dir="$(python3 - "$resolved_target_root" "$dir_entry" <<'PY' +from pathlib import Path, PurePosixPath +import os +import sys + +root = Path(sys.argv[1]).resolve(strict=True) +raw = sys.argv[2] +relative = PurePosixPath(raw) +if not raw or relative.is_absolute() or ".." in relative.parts: + raise SystemExit(1) + +candidate = root.joinpath(*relative.parts) +probe = root +for part in relative.parts: + if part in {"", "."}: + continue + probe = probe / part + if probe.is_symlink(): + raise SystemExit(1) + +resolved = candidate.resolve(strict=True) +try: + resolved.relative_to(root) +except ValueError: + raise SystemExit(1) +if not resolved.is_dir() or os.path.commonpath((str(root), str(resolved))) != str(root): + raise SystemExit(1) +print(resolved) +PY +)"; then + echo "ERROR: STRIX_SOURCE_DIRS entry '$dir_entry' must resolve to a non-symlink directory inside the scan target." >&2 + return 1 fi + resolved_dirs+=("$resolved_source_dir") done - set +f if [ "${#resolved_dirs[@]}" -eq 0 ]; then return 1 @@ -3849,8 +3920,14 @@ run_current_target_scan() { local strict_primary_provider_fallback=0 if [ "$INFRA_ERROR_DETECTED" -eq 1 ] && provider_signal_fail_closed_enabled; then - if is_model_retryable_error "$PRIMARY_MODEL" && has_distinct_fallback_model_for_model "$PRIMARY_MODEL"; then - strict_primary_provider_fallback=1 + if is_model_retryable_error "$PRIMARY_MODEL"; then + if has_distinct_fallback_model_for_model "$PRIMARY_MODEL"; then + strict_primary_provider_fallback=1 + else + report_no_distinct_fallback_model "$PRIMARY_MODEL" + echo "Strix scan failed after provider infrastructure or failure-signal output; failing closed." >&2 + return 1 + fi else echo "Strix scan failed after provider infrastructure or failure-signal output; failing closed." >&2 return 1 @@ -3868,7 +3945,7 @@ run_current_target_scan() { fi case "$PR_FINDINGS_DECISION" in - block_changed | block_unmapped | block_manifest_unverified) + block_changed | block_unmapped | block_manifest_unverified | block_baseline_unproven) if [ "$strict_primary_provider_fallback" -eq 1 ]; then fail_reported_vulnerabilities_before_fallback_success || true fi @@ -3944,7 +4021,7 @@ run_current_target_scan() { fi case "$PR_FINDINGS_DECISION" in - block_changed | block_unmapped | block_manifest_unverified) + block_changed | block_unmapped | block_manifest_unverified | block_baseline_unproven) if [ "$strict_fallback_provider_signal" -eq 1 ]; then fail_reported_vulnerabilities_before_fallback_success || true fi @@ -3978,18 +4055,7 @@ run_current_target_scan() { fi if [ "$fallback_tried" -eq 0 ]; then - local fallback_config_name - fallback_config_name="$(fallback_models_config_name_for_model "$PRIMARY_MODEL")" - local configured_fallback_count=0 - for candidate_raw in "${FALLBACK_MODELS[@]}"; do - candidate="$(normalize_model "$candidate_raw")" - [ -n "$candidate" ] && configured_fallback_count=$((configured_fallback_count + 1)) - done - if [ "$configured_fallback_count" -eq 0 ]; then - echo "ERROR: No fallback models configured ($fallback_config_name is empty). Configure distinct models." >&2 - else - echo "ERROR: All configured fallback models are the same as the primary model" >&2 - fi + report_no_distinct_fallback_model "$PRIMARY_MODEL" return 1 fi diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index aae63c5d..67f2f14c 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -25,9 +25,9 @@ unset LITELLM_API_KEY unset LITELLM_MASTER_KEY unset GEMINI_API_KEY unset GOOGLE_APPLICATION_CREDENTIALS -if ! python3 -c 'import pathlib' >/dev/null 2>&1; then - export PATH="/opt/homebrew/bin:/usr/bin:/bin:$PATH" -fi +# Prefer stable system/package-manager runtimes over mutable version-manager +# shims while the test suite exercises PATH-hijack defenses. +export PATH="/opt/homebrew/bin:/usr/bin:/bin:$PATH" record_failure() { echo "FAIL: $1" >&2 @@ -44,6 +44,16 @@ assert_equals() { fi } +sha256_file() { + python3 - "$1" <<'PY' +import hashlib +from pathlib import Path +import sys + +print(hashlib.sha256(Path(sys.argv[1]).read_bytes()).hexdigest()) +PY +} + print_assertion_source() { local file_path="$1" @@ -440,7 +450,8 @@ assert_changed_file_membership_uses_cached_normalized_paths() { assert_absent_endpoint_search_uses_canonical_target_path() { assert_file_contains "$GATE_SCRIPT" 'resolved_target_root="$(resolve_current_target_path "$TARGET_PATH" 2>/dev/null)"' "absent-endpoint search resolves canonical target root" - assert_file_contains "$GATE_SCRIPT" 'candidate="${resolved_target_root%/}/$dir_entry"' "absent-endpoint search uses canonical target root" + assert_file_contains "$GATE_SCRIPT" 'resolved.relative_to(root)' "absent-endpoint search enforces the canonical target boundary" + assert_file_contains "$GATE_SCRIPT" 'if probe.is_symlink()' "absent-endpoint search rejects symlink path components" assert_file_not_contains "$GATE_SCRIPT" 'candidate="${TARGET_PATH%/}/$dir_entry"' "absent-endpoint search avoids relative target path roots" } @@ -899,8 +910,11 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'umask 022' "Strix workflow creates the credential-bearing executable without group/world write access" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'chmod go-w -- "$strix_scripts_root" "$strix_executable"' "Strix workflow normalizes the installation root and resolved executable before hashing" assert_file_contains "$GATE_SCRIPT" 'STRIX_EXECUTABLE_PATH must name the trusted installed Strix executable' "Strix gate requires an explicit trusted executable path" + assert_file_contains "$GATE_SCRIPT" 'every Strix invocation requires a pinned executable root and SHA-256 digest' "Strix gate requires executable integrity outside PR evidence mode too" assert_file_contains "$GATE_SCRIPT" 'did not match the pinned SHA-256 digest' "Strix gate rejects executable substitution after trusted installation" assert_file_contains "$GATE_SCRIPT" 'STRIX_EXECUTABLE_PATH must be outside the untrusted scan target' "Strix executable cannot come from the scan target" + assert_file_contains "$GATE_SCRIPT" 'child_env["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"' "Strix child shebang resolution uses a fixed system PATH" + assert_file_not_contains "$GATE_SCRIPT" 'child_env["PATH"] = os.environ.get("PATH"' "Strix child never inherits an attacker-extended PATH" assert_file_not_contains "$GATE_SCRIPT" 'shutil.which("strix")' "Strix gate never resolves its credential-bearing executable through inherited PATH" assert_file_not_contains "$workflow_file" "https://sh.rustup.rs" "coverage refuses a mutable Rust network installer" assert_file_contains "$workflow_file" "cargo-llvm-cov-x86_64-unknown-linux-musl.tar.gz" "coverage pins the official cargo-llvm-cov 0.8.7 Linux asset" @@ -3070,7 +3084,7 @@ run_gate_case() { local raw_llm_api_base_override="${10-__DEFAULT__}" local initial_llm_api_base="${11-}" - local raw_llm_api_base="https://example.invalid/generateContent" + local raw_llm_api_base="" if [ "$raw_llm_api_base_override" != "__DEFAULT__" ]; then raw_llm_api_base="$raw_llm_api_base_override" elif [ "$default_provider" = "openai" ]; then @@ -3188,7 +3202,7 @@ printf '%s\n' "$target_path" >> "${FAKE_STRIX_TARGET_LOG:?}" STRIX_REPORTS_DIR="${STRIX_REPORTS_DIR:-strix_runs}" case "${FAKE_STRIX_SCENARIO:?}" in -success|runtime-env-forwarding|vertex-primary-success-timing-message|direct-openai-gpt-does-not-require-github-models-api-base|pr-executable-integrity-mismatch|pr-executable-group-writable) +success|runtime-env-forwarding|vertex-primary-success-timing-message|direct-openai-gpt-does-not-require-github-models-api-base|pr-executable-integrity-mismatch|pr-executable-group-writable|missing-executable-integrity) echo "scan ok" exit 0 ;; @@ -3629,7 +3643,7 @@ REPORT ;; esac ;; - github-models-fallback-provider-signal-tries-next | github-models-fallback-baseline-vulnerability-before-next-success-continues | github-models-fallback-changed-vulnerability-before-next-success-blocks | github-models-fallback-dockerfile-test-baseline-before-next-success-continues) + github-models-fallback-provider-signal-tries-next | github-models-fallback-baseline-vulnerability-before-next-success-blocks | github-models-fallback-changed-vulnerability-before-next-success-blocks | github-models-fallback-dockerfile-test-baseline-before-next-success-blocks) case "${STRIX_LLM:-}" in openai/gpt-5) echo "LLM CONNECTION FAILED" @@ -3638,7 +3652,7 @@ REPORT exit 1 ;; openai/deepseek/deepseek-r1-0528) - if [ "${FAKE_STRIX_SCENARIO:?}" = "github-models-fallback-baseline-vulnerability-before-next-success-continues" ]; then + if [ "${FAKE_STRIX_SCENARIO:?}" = "github-models-fallback-baseline-vulnerability-before-next-success-blocks" ]; then mkdir -p "$STRIX_REPORTS_DIR/fake-pr-baseline-provider-signal/vulnerabilities" cat >"$STRIX_REPORTS_DIR/fake-pr-baseline-provider-signal/vulnerabilities/vuln-0001.md" <<'EOS' Severity: CRITICAL @@ -3652,7 +3666,7 @@ Severity: CRITICAL Location 1: sync-module-system/smart-crawling-biz/src/main/java/org/empasy/sync/modules/system/controller/SysPositionController.java:12 EOS - elif [ "${FAKE_STRIX_SCENARIO:?}" = "github-models-fallback-dockerfile-test-baseline-before-next-success-continues" ]; then + elif [ "${FAKE_STRIX_SCENARIO:?}" = "github-models-fallback-dockerfile-test-baseline-before-next-success-blocks" ]; then mkdir -p "$STRIX_REPORTS_DIR/fake-pr-dockerfile-test-provider-signal/vulnerabilities" cat >"$STRIX_REPORTS_DIR/fake-pr-dockerfile-test-provider-signal/vulnerabilities/vuln-0001.md" <<'EOS' Severity: MEDIUM @@ -4149,12 +4163,32 @@ EOS ;; esac ;; - nonvertex-slash-model-not-rewritten) - if [ "${STRIX_LLM:-}" = "deepseek/models/deepseek-r1" ]; then - echo "scan ok with deepseek model passthrough" + notfound-high-log-low-artifact) + mkdir -p "$STRIX_REPORTS_DIR/fake-notfound-partial/vulnerabilities" + cat >"$STRIX_REPORTS_DIR/fake-notfound-partial/vulnerabilities/vuln-0001.md" <<'EOS' +Severity: LOW +EOS + echo "Severity: HIGH" + echo "Error: litellm.NotFoundError: Vertex_aiException - x" + echo '"status": "NOT_FOUND"' + echo "Penetration test failed after emitting a HIGH log-only finding" + exit 1 + ;; + source-dirs-relative-escape) + mkdir -p "$STRIX_REPORTS_DIR/fake-source-dir-escape/vulnerabilities" + cat >"$STRIX_REPORTS_DIR/fake-source-dir-escape/vulnerabilities/vuln-0001.md" <<'EOS' +Severity: HIGH +Endpoint: /api/outside-secret +EOS + echo "litellm.exceptions.Timeout: provider timed out after a partial report" + exit 1 + ;; + nonvertex-slash-model-not-rewritten-as-vertex) + if [ "${STRIX_LLM:-}" = "openai/deepseek/models/deepseek-r1" ]; then + echo "scan ok with non-Vertex deepseek GitHub Models routing" exit 0 fi - echo "Error: deepseek model was rewritten (${STRIX_LLM:-})" >&2 + echo "Error: deepseek model routing was corrupted (${STRIX_LLM:-})" >&2 exit 33 ;; preserve-existing-api-base) @@ -5011,6 +5045,8 @@ EOS esac EOF chmod +x "$fake_strix" + local fake_strix_sha256 + fake_strix_sha256="$(sha256_file "$fake_strix")" cat >"$fake_gh" <<'EOF' #!/usr/bin/env bash @@ -5091,6 +5127,10 @@ EOF echo 'GET /api/hidden-secret' >"$repo_root_dir/.git/refs/leaked.txt" mkdir -p "$repo_root_dir/node_modules/fake-pkg" echo 'GET /api/hidden-secret' >"$repo_root_dir/node_modules/fake-pkg/index.js" + elif [ "$scenario" = "source-dirs-relative-escape" ]; then + mkdir -p "$workspace_dir/outside" + echo 'GET /api/outside-secret' >"$workspace_dir/outside/routes.txt" + echo 'print("changed")' >"$repo_root_dir/src/routes.py" elif [ "$scenario" = "pr-stale-source-claim-fallback-success" ]; then mkdir -p "$repo_root_dir/backend/db" cat >"$repo_root_dir/backend/db/models.py" <<'EOS' @@ -5211,7 +5251,7 @@ EOS touch "$repo_root_dir/docker-compose.yml" touch "$repo_root_dir/render.yaml" echo '0.0.0' >"$repo_root_dir/VERSION" - elif [ "$scenario" = "github-models-fallback-dockerfile-test-baseline-before-next-success-continues" ]; then + elif [ "$scenario" = "github-models-fallback-dockerfile-test-baseline-before-next-success-blocks" ]; then mkdir -p "$repo_root_dir/.github/workflows" cat >"$repo_root_dir/.github/workflows/build-ci-image.yml" <<'EOS' name: Build CI image @@ -5325,6 +5365,8 @@ PY local env_cmd=( PATH="$untrusted_bin_dir:$bin_dir:$PATH" STRIX_EXECUTABLE_PATH="$fake_strix" + STRIX_EXECUTABLE_ROOT="$bin_dir" + STRIX_EXECUTABLE_SHA256="$fake_strix_sha256" FAKE_STRIX_PATH_HIJACK_LOG="$path_hijack_log" STRIX_INPUT_FILE_ROOT="$tmp_dir" GITHUB_EVENT_NAME="" @@ -5362,15 +5404,6 @@ PY ) fi if [ "$scenario" = "pr-executable-root-group-writable" ]; then - local fake_strix_sha256 - fake_strix_sha256="$(python3 - "$fake_strix" <<'PY' -import hashlib -from pathlib import Path -import sys - -print(hashlib.sha256(Path(sys.argv[1]).read_bytes()).hexdigest()) -PY -)" env_cmd+=( IS_PR_EVIDENCE_RUN="true" STRIX_EXECUTABLE_ROOT="$bin_dir" @@ -5381,6 +5414,19 @@ PY if [ "$scenario" = "pr-executable-group-writable" ]; then chmod 0775 "$fake_strix" fi + if [ "$scenario" = "missing-executable-integrity" ]; then + local integrity_env_cmd=() + local integrity_env_pair + for integrity_env_pair in "${env_cmd[@]}"; do + case "$integrity_env_pair" in + STRIX_EXECUTABLE_ROOT=* | STRIX_EXECUTABLE_SHA256=*) + continue + ;; + esac + integrity_env_cmd+=("$integrity_env_pair") + done + env_cmd=("${integrity_env_cmd[@]}") + fi if [ "$scenario" = "report-known-internal-warning-sanitized" ]; then env_cmd+=( FAKE_STRIX_OUTSIDE_REPORT_DIR="$repo_root_dir/outside-strix-report" @@ -5652,11 +5698,23 @@ run_filtered_gate_case_if_requested() { "vertex_ai/ready-primary" \ "" ;; + nonvertex-slash-model-not-rewritten-as-vertex) + run_gate_case "nonvertex-slash-model-not-rewritten-as-vertex" \ + "deepseek/models/deepseek-r1" \ + "vertex_ai/fallback-one" \ + "0" \ + "scan ok with non-Vertex deepseek GitHub Models routing" \ + "1" \ + "openai/deepseek/models/deepseek-r1" \ + "https://models.github.ai/inference" \ + "vertex_ai" \ + "https://models.github.ai/inference" + ;; pr-executable-integrity-mismatch) run_gate_case "pr-executable-integrity-mismatch" \ "vertex_ai/ready-primary" \ "" \ - "1" \ + "2" \ "did not match the pinned SHA-256 digest" \ "0" \ "" \ @@ -5666,7 +5724,7 @@ run_filtered_gate_case_if_requested() { run_gate_case "pr-executable-group-writable" \ "vertex_ai/ready-primary" \ "" \ - "1" \ + "2" \ "must not be group/world writable" \ "0" \ "" \ @@ -5676,12 +5734,79 @@ run_filtered_gate_case_if_requested() { run_gate_case "pr-executable-root-group-writable" \ "vertex_ai/ready-primary" \ "" \ - "1" \ + "2" \ "pinned Strix installation root must not be group/world writable" \ "0" \ "" \ "" ;; + missing-executable-integrity) + run_gate_case "missing-executable-integrity" \ + "vertex_ai/ready-primary" \ + "" \ + "2" \ + "every Strix invocation requires a pinned executable root and SHA-256 digest" \ + "0" \ + "" \ + "" + ;; + reject-arbitrary-api-base) + run_gate_case "reject-arbitrary-api-base" \ + "openai/gpt-4o-mini" \ + "" \ + "2" \ + "LLM_API_BASE must use the pinned GitHub Models inference endpoint; arbitrary provider hosts are forbidden." \ + "0" \ + "" \ + "" \ + "vertex_ai" \ + "" \ + "https://attacker.invalid/v1" + ;; + github-models-empty-api-base-file-rejected) + run_gate_case "github-models-empty-api-base-file-rejected" \ + "openai/gpt-5" \ + "" \ + "2" \ + "GitHub Models Strix scans require a non-empty LLM_API_BASE_FILE" \ + "0" \ + "" \ + "" \ + "openai" \ + " " + ;; + notfound-high-log-low-artifact) + run_gate_case "notfound-high-log-low-artifact" \ + "vertex_ai/notfound-high-log-low-primary" \ + "vertex_ai/notfound-high-log-low-primary" \ + "1" \ + "Strix scan failed after provider infrastructure or failure-signal output; failing closed." \ + "1" \ + "vertex_ai/notfound-high-log-low-primary" \ + "" + ;; + source-dirs-relative-escape) + run_gate_case_allow_provider_signal "source-dirs-relative-escape" \ + "vertex_ai/source-dir-escape-primary" \ + "vertex_ai/source-dir-escape-primary" \ + "1" \ + "STRIX_SOURCE_DIRS entry '../outside' must resolve to a non-symlink directory inside the scan target." \ + "1" \ + "vertex_ai/source-dir-escape-primary" \ + "" \ + "vertex_ai" \ + "__DEFAULT__" \ + "" \ + "0" \ + "CRITICAL" \ + "0" \ + "" \ + "../outside" \ + "1200" \ + "0" \ + "pull_request" \ + "src/routes.py" + ;; vertex-primary-hallucinated-endpoint-fallback-success) run_gate_case "vertex-primary-hallucinated-endpoint-fallback-success" \ "vertex_ai/hallucination-primary" \ @@ -5796,7 +5921,7 @@ run_filtered_gate_case_if_requested() { "REGEX:Strix quick scan succeeded with fallback model 'gemini/fallback-one' in [0-9]+s\\." \ "2" \ "gemini/timeout-fallback-primary|gemini/fallback-one" \ - "https://example.invalid|https://example.invalid" \ + "|" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -5911,15 +6036,15 @@ run_filtered_gate_case_if_requested() { total-timeout) run_total_timeout_case ;; - github-models-fallback-baseline-vulnerability-before-next-success-continues) - run_gate_case "github-models-fallback-baseline-vulnerability-before-next-success-continues" \ + github-models-fallback-baseline-vulnerability-before-next-success-blocks) + run_gate_case "github-models-fallback-baseline-vulnerability-before-next-success-blocks" \ "openai/gpt-5" \ "" \ - "0" \ - "REGEX:Strix quick scan succeeded with fallback model 'deepseek/deepseek-v3-0324' in [0-9]+s\\." \ - "3" \ - "openai/gpt-5|openai/deepseek/deepseek-r1-0528|openai/deepseek/deepseek-v3-0324" \ - "https://models.github.ai/inference|https://models.github.ai/inference|https://models.github.ai/inference" \ + "1" \ + "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." \ + "2" \ + "openai/gpt-5|openai/deepseek/deepseek-r1-0528" \ + "https://models.github.ai/inference|https://models.github.ai/inference" \ "openai" \ "https://models.github.ai/inference" \ "" \ @@ -5973,15 +6098,15 @@ run_filtered_gate_case_if_requested() { "deepseek/deepseek-r1-0528 deepseek/deepseek-v3-0324" \ "1" ;; - github-models-fallback-dockerfile-test-baseline-before-next-success-continues) - run_gate_case "github-models-fallback-dockerfile-test-baseline-before-next-success-continues" \ + github-models-fallback-dockerfile-test-baseline-before-next-success-blocks) + run_gate_case "github-models-fallback-dockerfile-test-baseline-before-next-success-blocks" \ "openai/gpt-5" \ "" \ - "0" \ - "REGEX:Strix quick scan succeeded with fallback model 'deepseek/deepseek-v3-0324' in [0-9]+s\\." \ - "3" \ - "openai/gpt-5|openai/deepseek/deepseek-r1-0528|openai/deepseek/deepseek-v3-0324" \ - "https://models.github.ai/inference|https://models.github.ai/inference|https://models.github.ai/inference" \ + "1" \ + "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." \ + "2" \ + "openai/gpt-5|openai/deepseek/deepseek-r1-0528" \ + "https://models.github.ai/inference|https://models.github.ai/inference" \ "openai" \ "https://models.github.ai/inference" \ "" \ @@ -6071,6 +6196,13 @@ run_filtered_gate_case_if_requested() { "Materialized PR-head changed-file scope" \ "repository_dispatch" ;; + pull-request-target-git-legal-punctuation-uses-head-blob) + run_pull_request_target_head_scope_case \ + "pull-request-target-git-legal-punctuation-uses-head-blob" \ + "src/Ugly, but legal, path for a project (long)/module.py" \ + "BASE_LEGAL_PUNCTUATION_SHOULD_NOT_BE_SCANNED" \ + "HEAD_LEGAL_PUNCTUATION_SHOULD_BE_SCANNED" + ;; *) record_failure "unknown STRIX_TEST_CASE_FILTER '${STRIX_TEST_CASE_FILTER:-}'" ;; @@ -6212,6 +6344,8 @@ EOF env -u GITHUB_EVENT_PATH \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ GITHUB_EVENT_NAME="$github_event_name" \ PR_NUMBER="123" \ @@ -6336,6 +6470,8 @@ EOS env -u GITHUB_EVENT_PATH \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ GITHUB_EVENT_NAME="pull_request_target" \ PR_BASE_SHA="$base_sha" \ @@ -6444,6 +6580,8 @@ EOF env -u GITHUB_EVENT_PATH \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ GITHUB_EVENT_NAME="pull_request_target" \ PR_BASE_SHA="$base_sha" \ @@ -6583,6 +6721,8 @@ EOF env -u GITHUB_EVENT_PATH \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ GITHUB_EVENT_NAME="pull_request_target" \ PR_BASE_SHA="$base_sha" \ @@ -6621,6 +6761,8 @@ EOF env -u GITHUB_EVENT_PATH \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ GITHUB_EVENT_NAME="pull_request" \ STRIX_TEST_CHANGED_FILES_OVERRIDE="$(printf '%s\n%s' '../outside.py' "$changed_file")" \ @@ -6829,6 +6971,8 @@ EOF env -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ GITHUB_EVENT_NAME="pull_request_target" \ PR_BASE_SHA="$base_sha" \ @@ -7022,6 +7166,8 @@ EOF env -u GITHUB_EVENT_PATH \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ GITHUB_EVENT_NAME="pull_request_target" \ PR_BASE_SHA="$base_sha" \ @@ -7120,6 +7266,8 @@ EOF env -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ GITHUB_EVENT_NAME="pull_request_target" \ PR_BASE_SHA="$base_sha" \ @@ -7254,6 +7402,8 @@ EOF env -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ REAL_GIT_PATH="$real_git" \ FAKE_GIT_FAIL_COMMAND="$fake_git_fail_command" \ @@ -7348,6 +7498,8 @@ EOF env -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ GITHUB_EVENT_NAME="pull_request_target" \ PR_BASE_SHA="$base_sha" \ @@ -7435,6 +7587,8 @@ EOF env -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ GITHUB_EVENT_NAME="pull_request_target" \ PR_BASE_SHA="$base_sha" \ @@ -7509,6 +7663,8 @@ EOF env -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ GITHUB_EVENT_NAME="pull_request_target" \ PR_BASE_SHA="$base_sha" \ @@ -7581,6 +7737,8 @@ EOF env -u STRIX_TEST_PR_SCA_STATUS_OVERRIDE \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ GITHUB_EVENT_NAME="pull_request_target" \ GITHUB_EVENT_PATH="$event_payload_file" \ @@ -7665,6 +7823,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE -u STRIX_INPUT_FILE_ROOT \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ STRIX_DISABLE_PR_SCOPING="0" \ FAKE_STRIX_CHILD_PID_FILE="$child_pid_file" \ @@ -7744,6 +7904,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE -u STRIX_INPUT_FILE_ROOT \ PATH="$tmp_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$fake_strix" \ + STRIX_EXECUTABLE_ROOT="$(dirname "$fake_strix")" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$fake_strix")" \ STRIX_INPUT_FILE_ROOT="$allowed_input_dir" \ RUNNER_TEMP="$allowed_input_dir" \ FAKE_STRIX_CALL_LOG="$call_log" \ @@ -7796,6 +7958,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE -u STRIX_INPUT_FILE_ROOT \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ STRIX_DISABLE_PR_SCOPING="0" \ FAKE_STRIX_CALL_COUNT_FILE="$call_count_file" \ @@ -7869,6 +8033,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$tmp_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$fake_strix" \ + STRIX_EXECUTABLE_ROOT="$(dirname "$fake_strix")" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$fake_strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ STRIX_DISABLE_PR_SCOPING="0" \ STRIX_LLM_FILE="$strix_llm_file" \ @@ -7914,6 +8080,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$tmp_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$fake_strix" \ + STRIX_EXECUTABLE_ROOT="$(dirname "$fake_strix")" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$fake_strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ STRIX_TARGET_PATH="-" \ STRIX_DISABLE_PR_SCOPING="0" \ @@ -7968,6 +8136,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$tmp_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$fake_strix" \ + STRIX_EXECUTABLE_ROOT="$(dirname "$fake_strix")" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$fake_strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ STRIX_DISABLE_PR_SCOPING="0" \ STRIX_LLM_FILE="$strix_llm_file" \ @@ -8019,6 +8189,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$tmp_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$fake_strix" \ + STRIX_EXECUTABLE_ROOT="$(dirname "$fake_strix")" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$fake_strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ STRIX_DISABLE_PR_SCOPING="0" \ STRIX_LLM_FILE="$strix_llm_file" \ @@ -8062,6 +8234,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$tmp_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$fake_strix" \ + STRIX_EXECUTABLE_ROOT="$(dirname "$fake_strix")" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$fake_strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ STRIX_DISABLE_PR_SCOPING="0" \ STRIX_LLM_FILE="$strix_llm_file" \ @@ -8118,6 +8292,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE -u STRIX_INPUT_FILE_ROOT \ PATH="$tmp_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$fake_strix" \ + STRIX_EXECUTABLE_ROOT="$(dirname "$fake_strix")" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$fake_strix")" \ RUNNER_TEMP="$allowed_input_dir" \ FAKE_STRIX_CALL_LOG="$call_log" \ STRIX_DISABLE_PR_SCOPING="0" \ @@ -8175,6 +8351,8 @@ EOF env -u GITHUB_EVENT_PATH -u STRIX_INPUT_FILE_ROOT \ PATH="$tmp_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$fake_strix" \ + STRIX_EXECUTABLE_ROOT="$(dirname "$fake_strix")" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$fake_strix")" \ RUNNER_TEMP="$allowed_input_dir" \ GITHUB_EVENT_NAME="pull_request" \ STRIX_TEST_CHANGED_FILES_OVERRIDE=$'src/one.py\nsrc/two.py' \ @@ -8249,6 +8427,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE -u STRIX_INPUT_FILE_ROOT \ PATH="$tmp_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$fake_strix" \ + STRIX_EXECUTABLE_ROOT="$(dirname "$fake_strix")" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$fake_strix")" \ RUNNER_TEMP="$allowed_input_dir" \ FAKE_STRIX_CALL_LOG="$call_log" \ STRIX_DISABLE_PR_SCOPING="0" \ @@ -8294,9 +8474,9 @@ printf 'called\n' >"${FAKE_STRIX_CALL_LOG:?}" exit 0 EOF chmod +x "$fake_strix" - printf '%s' 'openai/gpt-4o-mini' >"$strix_llm_file" + printf '%s' 'openai/gpt-5' >"$strix_llm_file" printf '%s' 'dummy' >"$llm_api_key_file" - printf '%s' 'https://example.invalid/generateContent' >"$llm_api_base_file" + printf '%s' 'https://models.github.ai/inference' >"$llm_api_base_file" set +e ( @@ -8304,6 +8484,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$tmp_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$fake_strix" \ + STRIX_EXECUTABLE_ROOT="$(dirname "$fake_strix")" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$fake_strix")" \ RUNNER_TEMP="$inherited_runner_temp" \ STRIX_INPUT_FILE_ROOT="$explicit_input_root" \ FAKE_STRIX_CALL_LOG="$call_log" \ @@ -8353,9 +8535,9 @@ echo "Error: transport timeout" exit 1 EOF chmod +x "$fake_strix" - printf '%s' 'openai/gpt-4o-mini' >"$strix_llm_file" + printf '%s' 'openai/gpt-5' >"$strix_llm_file" printf '%s' 'dummy' >"$llm_api_key_file" - printf '%s' 'https://example.invalid/generateContent' >"$llm_api_base_file" + printf '%s' 'https://models.github.ai/inference' >"$llm_api_base_file" set +e ( @@ -8363,6 +8545,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$tmp_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$fake_strix" \ + STRIX_EXECUTABLE_ROOT="$(dirname "$fake_strix")" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$fake_strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ STRIX_DISABLE_PR_SCOPING="0" \ STRIX_LLM_FILE="$strix_llm_file" \ @@ -8409,9 +8593,9 @@ echo "Error: transport timeout" exit 1 EOF chmod +x "$fake_strix" - printf '%s' 'openai/gpt-4o-mini' >"$strix_llm_file" + printf '%s' 'openai/gpt-5' >"$strix_llm_file" printf '%s' 'dummy' >"$llm_api_key_file" - printf '%s' 'https://example.invalid/generateContent' >"$llm_api_base_file" + printf '%s' 'https://models.github.ai/inference' >"$llm_api_base_file" set +e ( @@ -8419,6 +8603,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$tmp_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$fake_strix" \ + STRIX_EXECUTABLE_ROOT="$(dirname "$fake_strix")" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$fake_strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ STRIX_DISABLE_PR_SCOPING="0" \ STRIX_LLM_FILE="$strix_llm_file" \ @@ -8459,9 +8645,9 @@ printf '%s\n' called >>"${FAKE_STRIX_CALL_LOG:?}" exit 0 EOF chmod +x "$fake_strix" - printf '%s' 'openai/gpt-4o-mini' >"$strix_llm_file" + printf '%s' 'openai/gpt-5' >"$strix_llm_file" printf '%s' 'dummy' >"$llm_api_key_file" - printf '%s' 'https://example.invalid/generateContent' >"$llm_api_base_file" + printf '%s' 'https://models.github.ai/inference' >"$llm_api_base_file" set +e ( @@ -8469,6 +8655,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$tmp_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$fake_strix" \ + STRIX_EXECUTABLE_ROOT="$(dirname "$fake_strix")" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$fake_strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ STRIX_DISABLE_PR_SCOPING="0" \ FAKE_STRIX_CALL_LOG="$call_log" \ @@ -8512,9 +8700,9 @@ printf 'called\n' >"${FAKE_STRIX_CALL_LOG:?}" exit 0 EOF chmod +x "$fake_strix" - printf '%s' 'openai/gpt-4o-mini' >"$strix_llm_file" + printf '%s' 'openai/gpt-5' >"$strix_llm_file" printf '%s' 'dummy' >"$llm_api_key_file" - printf '%s' 'https://example.invalid/generateContent' >"$llm_api_base_file" + printf '%s' 'https://models.github.ai/inference' >"$llm_api_base_file" set +e ( @@ -8522,6 +8710,8 @@ EOF env -u GITHUB_EVENT_NAME -u GITHUB_EVENT_PATH -u STRIX_TEST_CHANGED_FILES_OVERRIDE \ PATH="$bin_dir:$PATH" \ STRIX_EXECUTABLE_PATH="$bin_dir/strix" \ + STRIX_EXECUTABLE_ROOT="$bin_dir" \ + STRIX_EXECUTABLE_SHA256="$(sha256_file "$bin_dir/strix")" \ STRIX_INPUT_FILE_ROOT="$tmp_dir" \ FAKE_STRIX_CALL_LOG="$call_log" \ STRIX_LLM_FILE="$strix_llm_file" \ @@ -8653,6 +8843,12 @@ run_pull_request_target_head_scope_case \ "BASE_CONTENT_WITH_SPACE_SHOULD_NOT_BE_SCANNED" \ "HEAD_CONTENT_WITH_SPACE_SHOULD_BE_SCANNED" +run_pull_request_target_head_scope_case \ + "pull-request-target-git-legal-punctuation-uses-head-blob" \ + "src/Ugly, but legal, path for a project (long)/module.py" \ + "BASE_LEGAL_PUNCTUATION_SHOULD_NOT_BE_SCANNED" \ + "HEAD_LEGAL_PUNCTUATION_SHOULD_BE_SCANNED" + run_pull_request_target_head_scope_case \ "pull-request-target-nextjs-bracket-route-uses-head-blob" \ "frontend/src/app/labels/[slug]/page.tsx" \ @@ -8817,7 +9013,7 @@ run_gate_case "success-with-critical-report" \ run_gate_case "pr-executable-integrity-mismatch" \ "vertex_ai/ready-primary" \ "" \ - "1" \ + "2" \ "did not match the pinned SHA-256 digest" \ "0" \ "" \ @@ -8826,7 +9022,7 @@ run_gate_case "pr-executable-integrity-mismatch" \ run_gate_case "pr-executable-group-writable" \ "vertex_ai/ready-primary" \ "" \ - "1" \ + "2" \ "must not be group/world writable" \ "0" \ "" \ @@ -8835,12 +9031,21 @@ run_gate_case "pr-executable-group-writable" \ run_gate_case "pr-executable-root-group-writable" \ "vertex_ai/ready-primary" \ "" \ - "1" \ + "2" \ "pinned Strix installation root must not be group/world writable" \ "0" \ "" \ "" +run_gate_case "missing-executable-integrity" \ + "vertex_ai/ready-primary" \ + "" \ + "2" \ + "every Strix invocation requires a pinned executable root and SHA-256 digest" \ + "0" \ + "" \ + "" + run_gate_case "runtime-env-forwarding" \ "gemini/gemini-pro-3.1-preview" \ "" \ @@ -8874,10 +9079,10 @@ run_gate_case "nonrecoverable" \ "openai/gpt-4o-mini" \ "vertex_ai/fallback-one" \ "1" \ - "Strix quick scan failed with a non-recoverable error." \ + "Strix scan failed after provider infrastructure or failure-signal output; failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" + "" run_gate_case "provider-prefix-required" \ "gemini-2.5-pro" \ @@ -8909,11 +9114,11 @@ run_gate_case "provider-prefix-required-resource-path-primary-implicit-default-p run_gate_case "provider-prefix-required-resource-path-primary-explicit-empty-default-provider" \ "projects/p1/locations/us-central1/publishers/google/models/gemini-2.5-pro" \ "vertex_ai/fallback-one" \ + "2" \ + "Vertex resource paths require an explicit vertex_ai or vertex_ai_beta provider." \ "0" \ - "Normalized STRIX_LLM to provider-qualified model 'vertex_ai/gemini-2.5-pro'." \ - "1" \ - "vertex_ai/gemini-2.5-pro" \ - "" \ + "" \ + "" \ "" run_gate_case "provider-prefix-resource-path-primary-notfound-fallback-success" \ @@ -8962,7 +9167,7 @@ run_gate_case "nonvertex-slash-model-passthrough" \ "scan ok with non-vertex slash model passthrough" \ "1" \ "foo/bar" \ - "https://example.invalid" + "" run_gate_case "primary-duplicate-in-fallback" \ "missing-primary" \ @@ -9062,7 +9267,7 @@ run_gate_case_allow_provider_signal "vertex-primary-api-connection-retry-same-mo "scan ok after same-model api connection retry" \ "2" \ "gemini/retry-api-connection-primary|gemini/retry-api-connection-primary" \ - "https://example.invalid|https://example.invalid" \ + "|" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -9201,14 +9406,14 @@ run_gate_case "github-models-fallback-provider-signal-tries-next" \ "deepseek/deepseek-r1-0528 deepseek/deepseek-v3-0324" \ "1" -run_gate_case "github-models-fallback-baseline-vulnerability-before-next-success-continues" \ +run_gate_case "github-models-fallback-baseline-vulnerability-before-next-success-blocks" \ "openai/gpt-5" \ "" \ - "0" \ - "REGEX:Strix quick scan succeeded with fallback model 'deepseek/deepseek-v3-0324' in [0-9]+s\\." \ - "3" \ - "openai/gpt-5|openai/deepseek/deepseek-r1-0528|openai/deepseek/deepseek-v3-0324" \ - "https://models.github.ai/inference|https://models.github.ai/inference|https://models.github.ai/inference" \ + "1" \ + "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." \ + "2" \ + "openai/gpt-5|openai/deepseek/deepseek-r1-0528" \ + "https://models.github.ai/inference|https://models.github.ai/inference" \ "openai" \ "https://models.github.ai/inference" \ "" \ @@ -9261,14 +9466,14 @@ run_gate_case "github-models-fallback-changed-vulnerability-before-next-success- "deepseek/deepseek-r1-0528 deepseek/deepseek-v3-0324" \ "1" -run_gate_case "github-models-fallback-dockerfile-test-baseline-before-next-success-continues" \ +run_gate_case "github-models-fallback-dockerfile-test-baseline-before-next-success-blocks" \ "openai/gpt-5" \ "" \ - "0" \ - "REGEX:Strix quick scan succeeded with fallback model 'deepseek/deepseek-v3-0324' in [0-9]+s\\." \ - "3" \ - "openai/gpt-5|openai/deepseek/deepseek-r1-0528|openai/deepseek/deepseek-v3-0324" \ - "https://models.github.ai/inference|https://models.github.ai/inference|https://models.github.ai/inference" \ + "1" \ + "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." \ + "2" \ + "openai/gpt-5|openai/deepseek/deepseek-r1-0528" \ + "https://models.github.ai/inference|https://models.github.ai/inference" \ "openai" \ "https://models.github.ai/inference" \ "" \ @@ -9298,7 +9503,7 @@ run_gate_case_allow_provider_signal "gemini-high-demand-retry-same-model-success "scan ok after same-model high-demand retry" \ "2" \ "gemini/retry-high-demand-primary|gemini/retry-high-demand-primary" \ - "https://example.invalid|https://example.invalid" \ + "|" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -9311,7 +9516,7 @@ run_gate_case_allow_provider_signal "gemini-timeout-direct-fallback-success" \ "REGEX:Strix quick scan succeeded with fallback model 'gemini/fallback-one' in [0-9]+s\\." \ "2" \ "gemini/retry-timeout-primary|gemini/fallback-one" \ - "https://example.invalid|https://example.invalid" \ + "|" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -9324,7 +9529,7 @@ run_gate_case_allow_provider_signal "gemini-timeout-fallback-success" \ "REGEX:Strix quick scan succeeded with fallback model 'gemini/fallback-one' in [0-9]+s\\." \ "2" \ "gemini/timeout-fallback-primary|gemini/fallback-one" \ - "https://example.invalid|https://example.invalid" \ + "|" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -9337,7 +9542,7 @@ run_gate_case_allow_provider_signal "gemini-generic-fallback-success" \ "REGEX:Strix quick scan succeeded with fallback model 'gemini/fallback-one' in [0-9]+s\\." \ "2" \ "gemini/timeout-fallback-primary|gemini/fallback-one" \ - "https://example.invalid|https://example.invalid" \ + "|" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -9366,7 +9571,7 @@ run_gate_case_allow_provider_signal "gemini-zero-findings-timeout-fallback-allow "Strix reported zero vulnerabilities before provider infrastructure failure; failing closed because provider infrastructure failures are not clean scan evidence." \ "2" \ "gemini/zero-timeout-primary|gemini/fallback-one" \ - "https://example.invalid|https://example.invalid" \ + "|" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -9387,7 +9592,7 @@ run_gate_case_allow_provider_signal "pr-scope-zero-finding-does-not-leak" \ "Strix reported zero vulnerabilities before provider infrastructure failure; failing closed because provider infrastructure failures are not clean scan evidence." \ "1" \ "gemini/scope-zero-leak-primary" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -9410,7 +9615,7 @@ run_gate_case "service-unavailable-no-llm-marker-nonrecoverable" \ "Strix quick scan failed with a non-recoverable error." \ "1" \ "custom/service-unavailable-primary" \ - "https://example.invalid" \ + "" \ "custom" \ "__DEFAULT__" \ "" \ @@ -9924,7 +10129,7 @@ run_gate_case "medium-vuln-default-threshold" \ "Strix quick scan failed with a non-recoverable error." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10034,29 +10239,38 @@ run_gate_case "malformed-severity-marker-nonrecoverable" \ "vertex_ai/malformed-severity-primary" \ "" -# Bug 7: Model disagreement — primary produces CRITICAL, fallback produces LOW. -# The CRITICAL from the earlier report must NOT be ignored. -# Both models produce NOT_FOUND errors, so the gate exhausts fallbacks and -# reports "Configured Vertex model and fallback models were unavailable." -# The key assertion is exit 1: the CRITICAL finding is NOT downgraded to pass. +# Bug 7: A primary model's unmapped CRITICAL report must fail closed before a +# later fallback can downgrade or contradict it. run_gate_case "model-disagreement-critical-in-earlier-report" \ "vertex_ai/model-a" \ "vertex_ai/model-b" \ "1" \ "Strix quick scan failed with a non-recoverable error." \ - "2" \ - "vertex_ai/model-a|vertex_ai/model-b" \ - "|" + "1" \ + "vertex_ai/model-a" \ + "" -# Bug 4: deepseek/models/deepseek-r1 must NOT be rewritten to vertex_ai/deepseek-r1 -run_gate_case "nonvertex-slash-model-not-rewritten" \ +run_gate_case "notfound-high-log-low-artifact" \ + "vertex_ai/notfound-high-log-low-primary" \ + "vertex_ai/notfound-high-log-low-primary" \ + "1" \ + "Strix scan failed after provider infrastructure or failure-signal output; failing closed." \ + "1" \ + "vertex_ai/notfound-high-log-low-primary" \ + "" + +# Bug 4: deepseek/models/deepseek-r1 must NOT be rewritten to vertex_ai/deepseek-r1. +# The GitHub Models child process still receives its required openai/ compatibility wrapper. +run_gate_case "nonvertex-slash-model-not-rewritten-as-vertex" \ "deepseek/models/deepseek-r1" \ "vertex_ai/fallback-one" \ "0" \ - "scan ok with deepseek model passthrough" \ + "scan ok with non-Vertex deepseek GitHub Models routing" \ "1" \ - "deepseek/models/deepseek-r1" \ - "https://example.invalid" + "openai/deepseek/models/deepseek-r1" \ + "https://models.github.ai/inference" \ + "vertex_ai" \ + "https://models.github.ai/inference" # Regression: STRIX_TARGET_PATH=/src with default STRIX_SOURCE_DIRS (now ".") # must resolve to /src/. (i.e. /src itself), NOT /src/src. @@ -10101,14 +10315,35 @@ run_gate_case "multi-source-dirs-existing-endpoint" \ "" \ "src api" -run_gate_case "preserve-existing-api-base" \ - "openai/gpt-4o-mini" \ +run_gate_case_allow_provider_signal "source-dirs-relative-escape" \ + "vertex_ai/source-dir-escape-primary" \ + "vertex_ai/source-dir-escape-primary" \ + "1" \ + "STRIX_SOURCE_DIRS entry '../outside' must resolve to a non-symlink directory inside the scan target." \ + "1" \ + "vertex_ai/source-dir-escape-primary" \ + "" \ + "vertex_ai" \ + "__DEFAULT__" \ "" \ "0" \ - "scan ok with preserved api base" \ - "1" \ + "CRITICAL" \ + "0" \ + "" \ + "../outside" \ + "1200" \ + "0" \ + "pull_request" \ + "src/routes.py" + +run_gate_case "reject-arbitrary-api-base" \ "openai/gpt-4o-mini" \ - "https://preexisting.invalid" \ + "" \ + "2" \ + "LLM_API_BASE must use the pinned GitHub Models inference endpoint; arbitrary provider hosts are forbidden." \ + "0" \ + "" \ + "" \ "vertex_ai" \ "" \ "https://preexisting.invalid" @@ -10185,7 +10420,7 @@ run_gate_case "bare-timeout-no-provider-marker" \ "" \ "1" \ "custom/bare-timeout-model" \ - "https://example.invalid" \ + "" \ "custom" \ "__DEFAULT__" \ "" \ @@ -10216,7 +10451,7 @@ run_gate_case "httpx-read-timeout-no-provider-marker" \ "non-recoverable error" \ "1" \ "custom/httpx-timeout-no-ctx" \ - "https://example.invalid" \ + "" \ "custom" \ "__DEFAULT__" \ "" \ @@ -10247,7 +10482,7 @@ run_gate_case "httpcore-read-timeout-no-provider-marker" \ "non-recoverable error" \ "1" \ "custom/httpcore-timeout-no-ctx" \ - "https://example.invalid" \ + "" \ "custom" \ "__DEFAULT__" \ "" \ @@ -10360,7 +10595,7 @@ run_gate_case "pr-changed-scope-bounded" \ "scan ok with bounded changed-file scope" \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10381,7 +10616,7 @@ run_gate_case "pr-python-scope-context" \ "scan ok with python dependency scope" \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10402,7 +10637,7 @@ run_gate_case "pr-changed-scope-full" \ "Scoped pull request Strix scan to 3 changed file(s)." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10423,7 +10658,7 @@ run_gate_case "pr-changed-scope-full-set" \ "scan ok with full configured PR scope" \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10455,7 +10690,7 @@ run_gate_case "pr-large-scope-full-set" \ "scan ok with large full PR scope" \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10478,7 +10713,7 @@ run_gate_case "pr-changed-scope-includes-ci-dependency" \ "scan ok with CI support dependency" \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10520,7 +10755,7 @@ run_gate_case "pr-deployment-scope-entrypoint-context" \ "scan ok with deployment entrypoint context" \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10558,11 +10793,11 @@ run_gate_case "pr-empty-diff-skip" \ run_gate_case "pr-baseline-critical-unchanged" \ "openai/gpt-4o-mini" \ "" \ - "0" \ - "Strix findings are limited to unchanged files in this pull request; allowing pipeline continuation." \ + "1" \ + "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10579,11 +10814,11 @@ run_gate_case "pr-baseline-critical-unchanged" \ run_gate_case "pr-baseline-critical-absolute-target" \ "openai/gpt-4o-mini" \ "" \ - "0" \ - "Strix findings are limited to unchanged files in this pull request; allowing pipeline continuation." \ + "1" \ + "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10600,11 +10835,11 @@ run_gate_case "pr-baseline-critical-absolute-target" \ run_gate_case "pr-baseline-critical-extensionless-dockerfile-target" \ "openai/gpt-4o-mini" \ "" \ - "0" \ - "Strix findings are limited to unchanged files in this pull request; allowing pipeline continuation." \ + "1" \ + "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10621,11 +10856,11 @@ run_gate_case "pr-baseline-critical-extensionless-dockerfile-target" \ run_gate_case "pr-baseline-critical-subdir-target" \ "openai/gpt-4o-mini" \ "" \ - "0" \ - "Strix findings are limited to unchanged files in this pull request; allowing pipeline continuation." \ + "1" \ + "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10645,11 +10880,11 @@ run_gate_case "pr-baseline-critical-subdir-target" \ run_gate_case "pr-baseline-critical-subdir-boxed-target" \ "openai/gpt-4o-mini" \ "" \ - "0" \ - "Strix findings are limited to unchanged files in this pull request; allowing pipeline continuation." \ + "1" \ + "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10669,11 +10904,11 @@ run_gate_case "pr-baseline-critical-subdir-boxed-target" \ run_gate_case "pr-baseline-critical-subdir-endpoint" \ "openai/gpt-4o-mini" \ "" \ - "0" \ - "Strix findings are limited to unchanged files in this pull request; allowing pipeline continuation." \ + "1" \ + "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10693,11 +10928,11 @@ run_gate_case "pr-baseline-critical-subdir-endpoint" \ run_gate_case "pr-baseline-critical-subdir-endpoint-bare-filename" \ "openai/gpt-4o-mini" \ "" \ - "0" \ - "Strix findings are limited to unchanged files in this pull request; allowing pipeline continuation." \ + "1" \ + "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10717,11 +10952,11 @@ run_gate_case "pr-baseline-critical-subdir-endpoint-bare-filename" \ run_gate_case "pr-baseline-critical-subdir-narrative-backticked-file" \ "openai/gpt-4o-mini" \ "" \ - "0" \ - "Strix findings are limited to unchanged files in this pull request; allowing pipeline continuation." \ + "1" \ + "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10745,7 +10980,7 @@ run_gate_case "pr-critical-relative-path-escape-subdir-narrative-backticked-file "Unable to map Strix findings to changed files; failing closed for pull request." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10769,7 +11004,7 @@ run_gate_case "pr-critical-changed" \ "Strix finding intersects files changed in this pull request." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10786,11 +11021,11 @@ run_gate_case "pr-critical-changed" \ run_gate_case "pr-changed-file-nonintersecting-line" \ "openai/gpt-4o-mini" \ "" \ - "0" \ - "Strix findings are limited to unchanged files in this pull request; allowing pipeline continuation." \ + "1" \ + "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10810,7 +11045,7 @@ run_gate_case "pr-critical-changed-bracketed-next-route" \ "Strix finding intersects files changed in this pull request." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10831,7 +11066,7 @@ run_gate_case "pr-critical-changed-xml-file-location" \ "Strix finding intersects files changed in this pull request." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10852,7 +11087,7 @@ run_gate_case "pr-critical-changed-xml-file-location-space" \ "Strix finding intersects files changed in this pull request." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10869,11 +11104,11 @@ run_gate_case "pr-critical-changed-xml-file-location-space" \ run_gate_case "pr-baseline-critical-narrative-backticked-service-file" \ "openai/gpt-4o-mini" \ "" \ - "0" \ - "Strix findings are limited to unchanged files in this pull request; allowing pipeline continuation." \ + "1" \ + "Strix threshold finding claims only unchanged files, but report-selected locations are not trusted PR attribution evidence; failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10894,7 +11129,7 @@ run_gate_case "pr-critical-unmapped-arbitrary-backticked-service-file" \ "Unable to map Strix findings to changed files; failing closed for pull request." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10915,7 +11150,7 @@ run_gate_case "pr-critical-changed-absolute-target" \ "Strix finding intersects files changed in this pull request." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10936,7 +11171,7 @@ run_gate_case "pr-critical-changed-internal-dotdir-target" \ "Strix finding intersects files changed in this pull request." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10978,7 +11213,7 @@ run_gate_case "pr-critical-changed-subdir-target" \ "Strix finding intersects files changed in this pull request." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -11002,7 +11237,7 @@ run_gate_case "pr-critical-changed-subdir-endpoint" \ "Strix finding intersects files changed in this pull request." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -11026,7 +11261,7 @@ run_gate_case "pr-critical-path-escape-subdir-target" \ "Unable to map Strix findings to changed files; failing closed for pull request." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -11050,7 +11285,7 @@ run_gate_case "pr-critical-unmapped" \ "Unable to map Strix findings to changed files; failing closed for pull request." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -11071,7 +11306,7 @@ run_gate_case "pr-critical-unmapped-narrative-target" \ "Unable to map Strix findings to changed files; failing closed for pull request." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -11092,7 +11327,7 @@ run_gate_case "pr-critical-unmapped-other-workspace-repo" \ "Unable to map Strix findings to changed files; failing closed for pull request." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -11113,7 +11348,7 @@ run_gate_case "pr-critical-manifest-only-pom" \ "Strix changed-manifest threshold finding requires package and CVE remediation; pull-request-controlled SCA workflow results cannot override model evidence, so the scan is failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -11134,7 +11369,7 @@ run_gate_case "pr-critical-manifest-only-pom-test-override" \ "Strix changed-manifest threshold finding requires package and CVE remediation; pull-request-controlled SCA workflow results cannot override model evidence, so the scan is failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -11159,7 +11394,7 @@ run_gate_case "pr-critical-manifest-only-pom-same-head-different-pr" \ "Strix changed-manifest threshold finding requires package and CVE remediation; pull-request-controlled SCA workflow results cannot override model evidence, so the scan is failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -11186,7 +11421,7 @@ run_gate_case "pr-critical-manifest-only-pom-current-pr-authoritative" \ "Strix changed-manifest threshold finding requires package and CVE remediation; pull-request-controlled SCA workflow results cannot override model evidence, so the scan is failing closed." \ "1" \ "openai/gpt-4o-mini" \ - "https://example.invalid" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -11470,6 +11705,12 @@ assert_normalized_model \ "vertex_ai" \ "vertex_ai/gemini-2.5-pro" +assert_normalized_model \ + "non-vertex-slash-model-passthrough" \ + "deepseek/models/deepseek-r1" \ + "vertex_ai" \ + "deepseek/models/deepseek-r1" + assert_model_requires_vertex_auth "explicit-vertex" "vertex_ai/gemini-2.5-pro" "gemini" "0" assert_model_requires_vertex_auth "explicit-vertex-beta" "vertex_ai_beta/gemini-2.5-pro" "gemini" "0" assert_model_requires_vertex_auth "vertex-resource-path" "projects/my-proj/locations/us-central1/models/gemini-2.5-pro" "vertex_ai" "0" @@ -11516,6 +11757,17 @@ run_gate_case "github-models-openai-gpt-requires-api-base" \ "openai" \ "" +run_gate_case "github-models-empty-api-base-file-rejected" \ + "openai/gpt-5" \ + "" \ + "2" \ + "GitHub Models Strix scans require a non-empty LLM_API_BASE_FILE" \ + "0" \ + "" \ + "" \ + "openai" \ + " " + run_gate_case "direct-openai-gpt-does-not-require-github-models-api-base" \ "openai_direct/gpt-5.4" \ "" \