Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 5 additions & 25 deletions .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3153,7 +3153,7 @@ jobs:
"$schema": "https://opencode.ai/config.json",
"model": "github-models/deepseek/deepseek-r1-0528",
"small_model": "github-models/deepseek/deepseek-v3-0324",
"enabled_providers": ["openai", "openrouter", "github-models"],
"enabled_providers": ["openai", "github-models"],
"lsp": false,
"mcp": {},
"permission": {
Expand Down Expand Up @@ -3291,24 +3291,6 @@ jobs:
}
}
},
"openrouter": {
"npm": "@ai-sdk/openai-compatible",
"name": "OpenRouter",
"options": {
"baseURL": "https://openrouter.ai/api/v1",
"apiKey": "{env:OPENROUTER_API_KEY}"
},
"models": {
"openrouter/free": {
"name": "OpenRouter Free",
"tool_call": true,
"limit": {
"context": 128000,
"output": 4096
}
}
}
},
"github-models": {
"npm": "@ai-sdk/openai-compatible",
"name": "GitHub Models",
Expand Down Expand Up @@ -3531,20 +3513,18 @@ jobs:
# model a working, un-throttled backend. Resolves {env:OPENAI_API_KEY}
# in the opencode.jsonc "openai" provider block.
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
SHARE: "false"
NPM_CONFIG_IGNORE_SCRIPTS: "true"
NO_COLOR: "1"
# High-sensitivity review candidates only. DeepSeek V3 has been the
# most reliable first-pass reviewer in the org queue, then the pool
# falls through to the direct GPT-5.6 Luna slot, OpenRouter's free
# compatibility endpoint, then the full-size GPT-4.1 long-context
# endpoint and provider-specific GPT/o3 fallbacks.
# falls through to the direct GPT-5.6 Luna slot, 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: "github-models/deepseek/deepseek-v3-0324 openai/gpt-5.6-luna openrouter/openrouter/free 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: "github-models/deepseek/deepseek-v3-0324 openai/gpt-5.6-luna 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.
Expand Down Expand Up @@ -5521,7 +5501,7 @@ jobs:
".github/workflows/strix.yml" \
"scripts/ci/test_strix_quick_gate.sh"
emit_known_missing_string_finding \
"STRIX_LLM must select GitHub Models openai/gpt-5 or newer, direct OpenAI GPT-5.4 or newer, OpenRouter openrouter/free, or an approved organization Vertex AI model" \
"STRIX_LLM must select GitHub Models openai/gpt-5 or newer, direct OpenAI GPT-5.4 or newer, or an approved organization Vertex AI model" \
"Strix unsupported-model errors must name the allowed providers" \
".github/workflows/strix.yml" \
"scripts/ci/test_strix_quick_gate.sh"
Expand Down
40 changes: 7 additions & 33 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,6 @@ jobs:
env:
STRIX_MODEL: ${{ github.event.client_payload.strix_llm || 'gpt-5.6-luna' }}
STRIX_OPENAI_API_KEY: ${{ secrets.STRIX_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
STRIX_OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
STRIX_VERTEX_CREDENTIALS: ${{ secrets.GCP_SA_KEY }}
STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }}
run: |
Expand Down Expand Up @@ -459,16 +458,6 @@ jobs:
exit 1
fi
;;
openrouter/free | openrouter/openrouter/free)
echo 'enabled=true' >> "$GITHUB_OUTPUT"
echo 'provider_mode=openrouter' >> "$GITHUB_OUTPUT"
sanitized_openrouter_key="$(printf '%s' "$STRIX_OPENROUTER_API_KEY" | tr -d '\r\n')"
trimmed_openrouter_key="$(printf '%s' "$sanitized_openrouter_key" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
if [ -z "$trimmed_openrouter_key" ]; then
echo '::error::OPENROUTER_API_KEY is required for Strix OpenRouter scans.'
exit 1
fi
;;
vertex_ai/gemini-3.1-pro-preview-customtools | vertex_ai/gemini-2.5-flash)
echo 'enabled=true' >> "$GITHUB_OUTPUT"
echo 'provider_mode=vertex_ai' >> "$GITHUB_OUTPUT"
Expand All @@ -480,7 +469,7 @@ jobs:
fi
;;
*)
echo '::error::STRIX_LLM must select GitHub Models openai/gpt-5 or newer, direct OpenAI GPT-5.4 or newer, OpenRouter openrouter/free, or an approved organization Vertex AI model.'
echo '::error::STRIX_LLM must select GitHub Models openai/gpt-5 or newer, direct OpenAI GPT-5.4 or newer, or an approved organization Vertex AI model.'
exit 1
;;
esac
Expand Down Expand Up @@ -550,7 +539,7 @@ jobs:
- name: Mask LLM API key
if: steps.gate.outputs.enabled == 'true'
env:
LLM_API_KEY: ${{ steps.gate.outputs.provider_mode == 'github_models' && (secrets.STRIX_GITHUB_MODELS_TOKEN || github.token) || steps.gate.outputs.provider_mode == 'openai_direct' && (secrets.STRIX_OPENAI_API_KEY || secrets.OPENAI_API_KEY) || steps.gate.outputs.provider_mode == 'openrouter' && secrets.OPENROUTER_API_KEY || '' }}
LLM_API_KEY: ${{ steps.gate.outputs.provider_mode == 'github_models' && (secrets.STRIX_GITHUB_MODELS_TOKEN || github.token) || steps.gate.outputs.provider_mode == 'openai_direct' && (secrets.STRIX_OPENAI_API_KEY || secrets.OPENAI_API_KEY) || '' }}
run: |
# Sanitize CR/LF before masking to prevent broken ::add-mask::
# commands and potential workflow command injection.
Expand All @@ -566,7 +555,7 @@ jobs:
- name: Prepare LLM API key input file
if: steps.gate.outputs.enabled == 'true'
env:
LLM_API_KEY_SECRET: ${{ steps.gate.outputs.provider_mode == 'github_models' && (secrets.STRIX_GITHUB_MODELS_TOKEN || github.token) || steps.gate.outputs.provider_mode == 'openai_direct' && (secrets.STRIX_OPENAI_API_KEY || secrets.OPENAI_API_KEY) || steps.gate.outputs.provider_mode == 'openrouter' && secrets.OPENROUTER_API_KEY || '' }}
LLM_API_KEY_SECRET: ${{ steps.gate.outputs.provider_mode == 'github_models' && (secrets.STRIX_GITHUB_MODELS_TOKEN || github.token) || steps.gate.outputs.provider_mode == 'openai_direct' && (secrets.STRIX_OPENAI_API_KEY || secrets.OPENAI_API_KEY) || '' }}
PROVIDER_MODE: ${{ steps.gate.outputs.provider_mode }}
run: |
sanitized="$(printf '%s' "$LLM_API_KEY_SECRET" | tr -d '\r\n')"
Expand All @@ -579,23 +568,11 @@ jobs:
echo '::error::STRIX_OPENAI_API_KEY is required for Strix OpenAI Platform scans.'
exit 1
fi
if [ -z "$trimmed" ] && [ "$PROVIDER_MODE" = "openrouter" ]; then
echo '::error::OPENROUTER_API_KEY is required for Strix OpenRouter scans.'
exit 1
fi
umask 077
llm_api_key_file="$RUNNER_TEMP/llm_api_key.txt"
printf '%s' "$trimmed" > "$llm_api_key_file"
printf '%s' "$sanitized" > "$llm_api_key_file"
echo "LLM_API_KEY_FILE=$llm_api_key_file" >> "$GITHUB_ENV"

- name: Prepare OpenRouter API base
if: steps.gate.outputs.provider_mode == 'openrouter'
run: |
umask 077
llm_api_base_file="$RUNNER_TEMP/llm_api_base.txt"
printf '%s' 'https://openrouter.ai/api/v1' > "$llm_api_base_file"
echo "LLM_API_BASE_FILE=$llm_api_base_file" >> "$GITHUB_ENV"

- name: Prepare GitHub Models API base
if: steps.gate.outputs.provider_mode == 'github_models'
run: |
Expand All @@ -605,7 +582,7 @@ jobs:
echo "LLM_API_BASE_FILE=$llm_api_base_file" >> "$GITHUB_ENV"

- name: Prepare GitHub Models fallback credentials
if: steps.gate.outputs.provider_mode == 'openai_direct' || steps.gate.outputs.provider_mode == 'openrouter'
if: steps.gate.outputs.provider_mode == 'openai_direct'
env:
GITHUB_MODELS_FALLBACK_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }}
run: |
Expand Down Expand Up @@ -706,14 +683,11 @@ jobs:
gpt-*)
printf 'openai_direct/%s' "$strix_model" > "$strix_llm_file"
;;
openrouter/free | openrouter/openrouter/free)
printf '%s' 'openrouter/free' > "$strix_llm_file"
;;
vertex_ai/gemini-3.1-pro-preview-customtools | vertex_ai/gemini-2.5-flash)
printf '%s' "$strix_model" > "$strix_llm_file"
;;
*)
echo '::error::STRIX_LLM must select GitHub Models openai/gpt-5 or newer, direct OpenAI GPT-5.4 or newer, OpenRouter openrouter/free, or an approved organization Vertex AI model.'
echo '::error::STRIX_LLM must select GitHub Models openai/gpt-5 or newer, direct OpenAI GPT-5.4 or newer, or an approved organization Vertex AI model.'
exit 1
;;
esac
Expand Down Expand Up @@ -750,7 +724,7 @@ jobs:
STRIX_LLM_MAX_RETRIES: 1
STRIX_TRANSIENT_RETRY_PER_MODEL: 2
STRIX_TRANSIENT_RETRY_BACKOFF_SECONDS: 60
STRIX_FALLBACK_MODELS: ${{ steps.gate.outputs.provider_mode == 'github_models' && 'github_models/openai/o3 github_models/openai/gpt-5-chat' || steps.gate.outputs.provider_mode == 'openai_direct' && 'github_models/openai/o3 github_models/openai/gpt-5-chat' || steps.gate.outputs.provider_mode == 'openrouter' && 'github_models/openai/o3 github_models/openai/gpt-5-chat' || '' }}
STRIX_FALLBACK_MODELS: ${{ steps.gate.outputs.provider_mode == 'github_models' && 'github_models/openai/o3 github_models/openai/gpt-5-chat' || steps.gate.outputs.provider_mode == 'openai_direct' && 'github_models/openai/o3 github_models/openai/gpt-5-chat' || '' }}
STRIX_GITHUB_MODELS_API_BASE_FILE: ${{ env.STRIX_GITHUB_MODELS_API_BASE_FILE }}
STRIX_GITHUB_MODELS_KEY_FILE: ${{ env.STRIX_GITHUB_MODELS_KEY_FILE }}
STRIX_FAIL_ON_PROVIDER_SIGNAL: "1"
Expand Down
8 changes: 8 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,11 @@
## 2026-07-09 - Avoid N+1 API blocking in SBOM aggregator
**Learning:** The `collect_inventories` function in `scripts/ci/sbom_inventory_aggregator.py` was fetching SBOMs from the GitHub dependency graph synchronously for every repository in the organization. For large organizations (up to 500 repos), this N+1 network/CLI bottleneck significantly stalled the aggregation workflow.
**Action:** Use `concurrent.futures.ThreadPoolExecutor` to fetch SBOMs concurrently when multiple repositories are provided, bounded by a `max_workers` limit (e.g., 10) to avoid overwhelming the CLI/API, while preserving the fast serial path for single-item inputs.

## 2024-05-20 - Fast-path pre-filter for O(N) un-trusted log parsing
**Learning:** The custom character-by-character parser in redact_sensitive_log.py safely avoids regex backtracking on untrusted logs, but blindly iterating through every log line is computationally wasteful for the vast majority of safe logs.
**Action:** Always include a fast-path regex pre-filter (like `SENSITIVE_KEY_RE.search(text)`) before initiating manual O(N) string parsing loops to skip safe inputs entirely.

## 2026-07-23 - GitHub Models Prefix Configuration parsing
**Learning:** Adding new provider prefixes like `openai_direct` mapping to GitHub Models or OpenAI required careful expansion of multiple case statements (`is_github_models_api_compatible_model`, etc) because `strix_quick_gate.sh` parses error signals explicitly bound to certain provider prefix mappings.
**Action:** When adding fallback aliases (like `openai_direct/*`) mapped to external or proxy APIs, remember to update `is_github_models_api_compatible_model` or similar verification filters in `strix_quick_gate.sh` so these new models correctly qualify as API compatible and properly route rate-limit / connection error signals.
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@
**Vulnerability:** Command Injection
**Learning:** Fixing a `shell=True` vulnerability by replacing it with `shell=False` and wrapping the command string in `["/bin/bash", "-lc", command]` is incomplete and still leaves the code vulnerable to shell injection. It acts as security theater, as it misleads linters while executing untrusted input via the bash wrapper. The vulnerability was still present in `sandboxed_web_e2e.py`.
**Prevention:** Remove `/bin/bash` wrapper from `subprocess` calls in CI scripts. Always use `shlex.split(command)` to safely parse strings into a list of arguments and pass the list directly to `subprocess.Popen` or `subprocess.run`.

## 2026-07-23 - OSV-Scan Dependency Hash Update
**Vulnerability:** Found `pyasn1` version 0.6.3 vulnerable to denial of service attacks (PYSEC-2026-3455, PYSEC-2026-3456, PYSEC-2026-3457) in `requirements-strix-ci-hashes.txt`.
**Learning:** Hard-coded versions and dependency hashes in `requirements-strix-ci-hashes.txt` (and similar lockfiles) prevent automatic updates. When a new vulnerability arises, it breaks CI pipelines (like pip-audit or OSV scanners) failing with an exit code 1.
**Prevention:** Monitor dependency audit pipeline failures and manually update both the version number (e.g. from `0.6.3` to `0.6.4`) AND the sha256 hashes generated from the official PyPI wheels and tarballs in the CI environment.
8 changes: 3 additions & 5 deletions requirements-strix-ci-hashes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1502,11 +1502,9 @@ protobuf==6.33.6 \
# grpcio-status
# proto-plus
pyasn1==0.6.4 \
--hash=sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81 \
--hash=sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b
# via
# -r requirements-strix-ci.txt
# pyasn1-modules
--hash=sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b \
--hash=sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81
# via pyasn1-modules
pyasn1-modules==0.4.2 \
--hash=sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a \
--hash=sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6
Expand Down
1 change: 0 additions & 1 deletion requirements-strix-ci.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@ google-cloud-aiplatform==1.133.0
protobuf<7.0.0
cryptography==49.0.0
python-multipart==0.0.31
pyasn1==0.6.4
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,7 @@ emit_known_missing_string_finding \
"scripts/ci/test_strix_quick_gate.sh"
emit_known_missing_string_finding \
"$EVIDENCE_FILE" \
"STRIX_LLM must select GitHub Models openai/gpt-5 or newer, direct OpenAI GPT-5.4 or newer, OpenRouter openrouter/free, or an approved organization Vertex AI model" \
"STRIX_LLM must select GitHub Models openai/gpt-5 or newer, direct OpenAI GPT-5.4 or newer, or an approved organization Vertex AI model" \
"Strix unsupported-model errors must name the allowed providers" \
".github/workflows/strix.yml" \
"scripts/ci/test_strix_quick_gate.sh"
Expand Down
17 changes: 13 additions & 4 deletions scripts/ci/redact_sensitive_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
from typing import Any

REDACTED = "[REDACTED]"
KEY_CHARS = frozenset("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-")
KEY_CHARS = frozenset(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-"
)
SENSITIVE_KEY_RE = re.compile(
r"(?:token|secret|password|passwd|credential|authorization|jwt|"
r"api[_-]?key|private[_-]?key|access[_-]?key|session[_-]?key)",
Expand All @@ -20,8 +22,7 @@
r"[A-Za-z0-9_-]{3,}(?![A-Za-z0-9_-])"
)
BEARER_RE = re.compile(
r"(?P<prefix>\b(?:authorization\s*:\s*)?(?:bearer|basic)\s+)"
r"[^\s\"'\\]+",
r"(?P<prefix>\b(?:authorization\s*:\s*)?(?:bearer|basic)\s+)" r"[^\s\"'\\]+",
re.IGNORECASE,
)
PROVIDER_TOKEN_RES = (
Expand Down Expand Up @@ -88,7 +89,11 @@ def _consume_sensitive_assignment(text: str, start: int) -> tuple[str, int] | No
elif char == value_quote:
break
else:
while cursor < len(text) and not text[cursor].isspace() and text[cursor] not in ",}":
while (
cursor < len(text)
and not text[cursor].isspace()
and text[cursor] not in ",}"
):
cursor += 1
if cursor == value_start:
return None
Expand All @@ -97,6 +102,10 @@ def _consume_sensitive_assignment(text: str, start: int) -> tuple[str, int] | No

def _redact_assignments(text: str) -> str:
"""Redact sensitive key/value assignments without backtracking regexes."""
# ⚡ Bolt: Fast-path return to skip O(N) assignment parsing for safe logs
if not SENSITIVE_KEY_RE.search(text):
return text

Comment on lines 103 to +108
output: list[str] = []
cursor = 0
while cursor < len(text):
Expand Down
11 changes: 0 additions & 11 deletions scripts/ci/run_opencode_review_model_pool.sh
Original file line number Diff line number Diff line change
Expand Up @@ -311,13 +311,6 @@ is_direct_openai_candidate() {
esac
}

is_openrouter_candidate() {
case "$1" in
openrouter/*) return 0 ;;
*) return 1 ;;
esac
}

is_low_sensitivity_candidate() {
case "$1" in
openai/*-mini | openai/*-nano | \
Expand All @@ -341,10 +334,6 @@ should_skip_model_candidate() {
printf 'Skipping OpenCode %s because OPENAI_API_KEY is not configured; falling back to the next provider-qualified candidate.\n' "$model_candidate"
return 0
fi
if is_openrouter_candidate "$model_candidate" && [ -z "${OPENROUTER_API_KEY:-}" ]; then
printf 'Skipping OpenCode %s because OPENROUTER_API_KEY is not configured; falling back to the next provider-qualified candidate.\n' "$model_candidate"
return 0
fi
return 1
}

Expand Down
10 changes: 5 additions & 5 deletions scripts/ci/strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,7 @@ is_github_models_model() {
is_github_models_api_compatible_model() {
case "$1" in
openai/openai/* | github_models/* | \
openai/o3 | openai/gpt-5* | openai/gpt-[6-9]* | openai/gpt-[1-9][0-9]* | \
openai/o3 | openai/gpt-5* | openai/gpt-[6-9]* | openai/gpt-[1-9][0-9]* | openai_direct/* | \
openai/deepseek/* | openai/meta/* | openai/mistral-ai/* | \
deepseek/* | meta/* | mistral-ai/*)
return 0
Expand Down Expand Up @@ -2224,10 +2224,10 @@ resolved_llm_api_base_for_model() {

local api_base_file="$LLM_API_BASE_FILE"
local api_base_file_name="LLM_API_BASE_FILE"
if is_github_models_model "$model" && [ -n "${STRIX_GITHUB_MODELS_API_BASE_FILE:-}" ]; then
# Cross-provider fallback: when the active primary provider uses a
# different API base (for example OpenRouter), github_models/* fallback
# attempts must still route through the GitHub Models inference endpoint.
if [ -z "$api_base_file" ] && is_github_models_model "$model" && [ -n "${STRIX_GITHUB_MODELS_API_BASE_FILE:-}" ]; then
# Cross-provider fallback: a direct-OpenAI primary run keeps its own
# key and no API base, while github_models/* fallback models route
# through the GitHub Models inference endpoint supplied here.
api_base_file="$STRIX_GITHUB_MODELS_API_BASE_FILE"
api_base_file_name="STRIX_GITHUB_MODELS_API_BASE_FILE"
fi
Expand Down
Loading
Loading