🎨 Palette: [UX improvement] 스티키 헤더를 위한 scroll-padding-top 추가 #21
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Strix Security Scan | |
| on: | |
| push: | |
| branches: [main, develop, master] | |
| pull_request_target: | |
| schedule: | |
| # Weekly scan on protected branches (Mondays at 03:00 UTC). | |
| - cron: '0 3 * * 1' | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: Optional pull request number for trusted PR-scope evidence | |
| required: false | |
| type: string | |
| pr_base_sha: | |
| description: Optional pull request base SHA for trusted PR-scope evidence | |
| required: false | |
| type: string | |
| pr_head_sha: | |
| description: Optional pull request head SHA for trusted PR-scope evidence | |
| required: false | |
| type: string | |
| strix_llm: | |
| description: Optional Strix model override for manual evidence runs | |
| required: false | |
| default: openai/gpt-5 | |
| type: string | |
| concurrency: | |
| group: >- | |
| strix-${{ github.repository }}-${{ github.event_name == 'pull_request_target' && | |
| format('pr-{0}-{1}', github.event.pull_request.number, github.event.pull_request.head.sha) || | |
| github.event.inputs.pr_number != '' && github.event.inputs.pr_head_sha != '' && | |
| format('pr-{0}-{1}', github.event.inputs.pr_number, github.event.inputs.pr_head_sha) || | |
| github.event.inputs.pr_number != '' && format('pr-{0}', github.event.inputs.pr_number) || github.ref }} | |
| # cancel-in-progress deliberately disabled: an attacker could force-push | |
| # a benign commit to cancel an in-progress scan of a malicious commit. The | |
| # head SHA in PR groups prevents stale scans from serializing newer evidence. | |
| cancel-in-progress: false | |
| permissions: | |
| actions: read | |
| contents: read | |
| models: read | |
| jobs: | |
| strix: | |
| timeout-minutes: 120 | |
| runs-on: ubuntu-latest | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| steps: | |
| - name: Harden runner | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| disable-file-monitoring: true | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: "3.13" | |
| - name: Resolve trusted Strix source ref | |
| id: trusted_source | |
| env: | |
| JOB_CONTEXT_JSON: ${{ toJSON(job) }} | |
| GITHUB_CONTEXT_JSON: ${{ toJSON(github) }} | |
| run: | | |
| set -euo pipefail | |
| python3 <<'PY' >>"$GITHUB_OUTPUT" | |
| import json | |
| import os | |
| import re | |
| import sys | |
| try: | |
| job_context = json.loads(os.environ.get("JOB_CONTEXT_JSON") or "{}") | |
| github_context = json.loads(os.environ.get("GITHUB_CONTEXT_JSON") or "{}") | |
| except json.JSONDecodeError as exc: | |
| print(f"::error::Could not parse GitHub workflow context JSON: {exc}", file=sys.stderr) | |
| raise SystemExit(1) | |
| trusted_repository = str( | |
| job_context.get("workflow_repository") or "ContextualWisdomLab/.github" | |
| ).strip() | |
| trusted_ref = str( | |
| job_context.get("workflow_sha") or github_context.get("workflow_sha") or "" | |
| ).strip() | |
| workflow_ref = str( | |
| job_context.get("workflow_ref") or github_context.get("workflow_ref") or "" | |
| ).strip() | |
| if not trusted_ref: | |
| trusted_ref = "main" | |
| prefix = "ContextualWisdomLab/.github/.github/workflows/strix.yml@" | |
| if workflow_ref.startswith(prefix): | |
| trusted_ref = workflow_ref.split("@", 1)[1] | |
| if not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", trusted_repository): | |
| print("::error::Trusted workflow repository resolved to an invalid name.", file=sys.stderr) | |
| raise SystemExit(1) | |
| if not re.fullmatch(r"[0-9a-fA-F]{40}|refs/[^\s]+|[A-Za-z0-9._/-]+", trusted_ref): | |
| print("::error::Trusted workflow ref resolved to an invalid value.", file=sys.stderr) | |
| raise SystemExit(1) | |
| print(f"repository={trusted_repository}") | |
| print(f"ref={trusted_ref}") | |
| PY | |
| - name: Checkout trusted Strix source | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| repository: ${{ steps.trusted_source.outputs.repository }} | |
| fetch-depth: 1 | |
| persist-credentials: false | |
| ref: ${{ steps.trusted_source.outputs.ref }} | |
| path: trusted-strix-source | |
| - name: Export trusted Strix source paths | |
| run: | | |
| set -euo pipefail | |
| trusted_strix_source="$GITHUB_WORKSPACE/trusted-strix-source" | |
| test -f "$trusted_strix_source/scripts/ci/strix_quick_gate.sh" | |
| test -f "$trusted_strix_source/scripts/ci/test_strix_quick_gate.sh" | |
| test -f "$trusted_strix_source/scripts/ci/strix_required_workflow_smoke.sh" | |
| { | |
| echo "TRUSTED_STRIX_SOURCE=$trusted_strix_source" | |
| echo "TRUSTED_STRIX_GATE=$trusted_strix_source/scripts/ci/strix_quick_gate.sh" | |
| echo "TRUSTED_STRIX_GATE_TEST=$trusted_strix_source/scripts/ci/test_strix_quick_gate.sh" | |
| echo "TRUSTED_STRIX_REQUIRED_SMOKE=$trusted_strix_source/scripts/ci/strix_required_workflow_smoke.sh" | |
| } >> "$GITHUB_ENV" | |
| - name: Materialize target workspace | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPOSITORY: ${{ github.repository }} | |
| TARGET_WORKSPACE_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || github.sha }} | |
| run: | | |
| set -euo pipefail | |
| trusted_workspace="$RUNNER_TEMP/trusted-workspace" | |
| mkdir -p "$trusted_workspace" | |
| git init -q "$trusted_workspace" | |
| gh auth setup-git | |
| git -C "$trusted_workspace" remote add origin "$GITHUB_SERVER_URL/$REPOSITORY.git" | |
| git -C "$trusted_workspace" fetch --no-tags --depth=1 origin "$TARGET_WORKSPACE_SHA" | |
| git -C "$trusted_workspace" checkout --detach --quiet "$TARGET_WORKSPACE_SHA" | |
| git -C "$trusted_workspace" cat-file -e "$TARGET_WORKSPACE_SHA^{commit}" | |
| { | |
| echo "TRUSTED_WORKSPACE=$trusted_workspace" | |
| } >> "$GITHUB_ENV" | |
| - name: Fetch pull request head for trusted scan | |
| if: github.event_name == 'pull_request_target' || github.event.inputs.pr_number != '' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PR_NUMBER: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event.inputs.pr_number }} | |
| PR_BASE_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }} | |
| PR_HEAD_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "$PR_NUMBER" ] || [ -z "$PR_HEAD_SHA" ]; then | |
| echo "::error::PR number and head SHA are required for trusted PR-scope Strix evidence." | |
| exit 1 | |
| fi | |
| gh auth setup-git | |
| if ! [[ "$PR_HEAD_SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then | |
| echo "::error::PR head SHA must be a 40-character git SHA." | |
| exit 1 | |
| fi | |
| if [ -n "$PR_BASE_SHA" ] && ! [[ "$PR_BASE_SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then | |
| echo "::error::PR base SHA must be a 40-character git SHA." | |
| exit 1 | |
| fi | |
| if [ -n "$PR_BASE_SHA" ]; then | |
| git -C "$TRUSTED_WORKSPACE" fetch --no-tags --depth=1 origin "$PR_BASE_SHA" | |
| git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_BASE_SHA^{commit}" | |
| fi | |
| # Fetching the expected head SHA directly avoids false failures when | |
| # refs/pull/<n>/head has already advanced before this queued run starts. | |
| if git -C "$TRUSTED_WORKSPACE" fetch --no-tags --depth=1 origin "$PR_HEAD_SHA"; then | |
| git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA^{commit}" | |
| if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:opencode.jsonc" 2>/dev/null; then | |
| git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:opencode.jsonc" > "$TRUSTED_WORKSPACE/opencode.jsonc" | |
| fi | |
| if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" 2>/dev/null; then | |
| git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" > "$TRUSTED_WORKSPACE/scripts/ci/pr_review_merge_scheduler.py" | |
| fi | |
| git -C "$TRUSTED_WORKSPACE" update-ref "refs/remotes/pull/${PR_NUMBER}/head" "$PR_HEAD_SHA" | |
| exit 0 | |
| fi | |
| for pr_head_fetch_attempt in 1 2 3 4 5 6; do | |
| git -C "$TRUSTED_WORKSPACE" fetch --no-tags --prune origin "+refs/pull/${PR_NUMBER}/head:refs/remotes/pull/${PR_NUMBER}/head" | |
| fetched_head_sha="$(git -C "$TRUSTED_WORKSPACE" rev-parse "refs/remotes/pull/${PR_NUMBER}/head")" | |
| if [ "$fetched_head_sha" = "$PR_HEAD_SHA" ]; then | |
| git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA^{commit}" | |
| if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:opencode.jsonc" 2>/dev/null; then | |
| git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:opencode.jsonc" > "$TRUSTED_WORKSPACE/opencode.jsonc" | |
| fi | |
| if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" 2>/dev/null; then | |
| git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" > "$TRUSTED_WORKSPACE/scripts/ci/pr_review_merge_scheduler.py" | |
| fi | |
| exit 0 | |
| fi | |
| if [ "$pr_head_fetch_attempt" -lt 6 ]; then | |
| echo "Fetched PR head $fetched_head_sha, expected $PR_HEAD_SHA; retrying after propagation delay." >&2 | |
| sleep 10 | |
| fi | |
| done | |
| echo "::error::PR head ref did not resolve to expected commit $PR_HEAD_SHA after retries." >&2 | |
| exit 1 | |
| - name: Self-test Strix required workflow contract | |
| timeout-minutes: 2 | |
| working-directory: trusted-strix-source | |
| run: | | |
| set -euo pipefail | |
| printf 'Running bounded Strix required-workflow smoke test.\n' | |
| bash "$TRUSTED_STRIX_REQUIRED_SMOKE" | |
| - name: Gate Strix secrets | |
| id: gate | |
| env: | |
| STRIX_MODEL: ${{ github.event.inputs.strix_llm || 'openai/gpt-5' }} | |
| STRIX_OPENAI_API_KEY: ${{ secrets.STRIX_OPENAI_API_KEY }} | |
| STRIX_VERTEX_CREDENTIALS: ${{ secrets.GCP_SA_KEY }} | |
| STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }} | |
| run: | | |
| strix_model="$(printf '%s' "$STRIX_MODEL" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" | |
| case "$strix_model" in | |
| openai/gpt-5-mini* | openai/gpt-5-nano* | \ | |
| openai/openai/gpt-5-mini* | openai/openai/gpt-5-nano* | \ | |
| github_models/openai/gpt-5-mini* | github_models/openai/gpt-5-nano*) | |
| echo '::error::STRIX_LLM must not select mini or nano GPT-5 variants for security evidence.' | |
| exit 1 | |
| ;; | |
| openai/gpt-5* | openai/gpt-[6-9]* | openai/gpt-[1-9][0-9]* | \ | |
| openai/openai/gpt-5* | openai/openai/gpt-[6-9]* | openai/openai/gpt-[1-9][0-9]* | \ | |
| github_models/openai/gpt-5* | github_models/openai/gpt-[6-9]* | github_models/openai/gpt-[1-9][0-9]*) | |
| echo 'enabled=true' >> "$GITHUB_OUTPUT" | |
| echo 'provider_mode=github_models' >> "$GITHUB_OUTPUT" | |
| sanitized_github_models_token="$(printf '%s' "$STRIX_GITHUB_MODELS_TOKEN" | tr -d '\r\n')" | |
| trimmed_github_models_token="$(printf '%s' "$sanitized_github_models_token" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" | |
| if [ -z "$trimmed_github_models_token" ]; then | |
| echo '::error::STRIX_GITHUB_MODELS_TOKEN is required for GitHub Models Strix scans.' | |
| exit 1 | |
| fi | |
| ;; | |
| gpt-5.[4-9]* | gpt-5.[1-9][0-9]* | gpt-[6-9]* | gpt-[1-9][0-9]* | \ | |
| openai-direct/gpt-5.[4-9]* | openai-direct/gpt-5.[1-9][0-9]* | openai-direct/gpt-[6-9]* | openai-direct/gpt-[1-9][0-9]*) | |
| echo 'enabled=true' >> "$GITHUB_OUTPUT" | |
| echo 'provider_mode=openai_direct' >> "$GITHUB_OUTPUT" | |
| sanitized_openai_key="$(printf '%s' "$STRIX_OPENAI_API_KEY" | tr -d '\r\n')" | |
| trimmed_openai_key="$(printf '%s' "$sanitized_openai_key" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" | |
| if [ -z "$trimmed_openai_key" ]; then | |
| echo '::error::STRIX_OPENAI_API_KEY is required for Strix OpenAI Platform 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" | |
| sanitized_vertex_credentials="$(printf '%s' "$STRIX_VERTEX_CREDENTIALS" | tr -d '\r')" | |
| trimmed_vertex_credentials="$(printf '%s' "$sanitized_vertex_credentials" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" | |
| if [ -z "$trimmed_vertex_credentials" ]; then | |
| echo '::error::GCP_SA_KEY is required for Vertex AI Strix scans.' | |
| exit 1 | |
| fi | |
| ;; | |
| *) | |
| 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 | |
| - name: Set up Python | |
| if: steps.gate.outputs.enabled == 'true' | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: "3.13" | |
| - name: Install Strix | |
| if: steps.gate.outputs.enabled == 'true' | |
| working-directory: trusted-strix-source | |
| run: | | |
| python3 -m pip install --disable-pip-version-check --no-cache-dir --require-hashes -r requirements-strix-ci-hashes.txt | |
| - 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 || '' }} | |
| run: | | |
| # Sanitize CR/LF before masking to prevent broken ::add-mask:: | |
| # commands and potential workflow command injection. | |
| sanitized="$(printf '%s' "$LLM_API_KEY" | tr -d '\r\n')" | |
| if [ -n "$sanitized" ]; then | |
| echo "::add-mask::${sanitized}" | |
| trimmed="$(printf '%s' "$sanitized" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" | |
| if [ -n "$trimmed" ] && [ "$trimmed" != "$sanitized" ]; then | |
| echo "::add-mask::${trimmed}" | |
| fi | |
| fi | |
| - 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 || '' }} | |
| PROVIDER_MODE: ${{ steps.gate.outputs.provider_mode }} | |
| run: | | |
| sanitized="$(printf '%s' "$LLM_API_KEY_SECRET" | tr -d '\r\n')" | |
| trimmed="$(printf '%s' "$sanitized" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" | |
| if [ -z "$trimmed" ] && [ "$PROVIDER_MODE" = "github_models" ]; then | |
| echo '::error::STRIX_GITHUB_MODELS_TOKEN is required for GitHub Models Strix scans.' | |
| exit 1 | |
| fi | |
| if [ -z "$trimmed" ] && [ "$PROVIDER_MODE" = "openai_direct" ]; then | |
| echo '::error::STRIX_OPENAI_API_KEY is required for Strix OpenAI Platform scans.' | |
| exit 1 | |
| fi | |
| umask 077 | |
| llm_api_key_file="$RUNNER_TEMP/llm_api_key.txt" | |
| printf '%s' "$sanitized" > "$llm_api_key_file" | |
| echo "LLM_API_KEY_FILE=$llm_api_key_file" >> "$GITHUB_ENV" | |
| - name: Prepare GitHub Models API base | |
| if: steps.gate.outputs.provider_mode == 'github_models' | |
| run: | | |
| umask 077 | |
| llm_api_base_file="$RUNNER_TEMP/llm_api_base.txt" | |
| printf '%s' 'https://models.github.ai/inference' > "$llm_api_base_file" | |
| echo "LLM_API_BASE_FILE=$llm_api_base_file" >> "$GITHUB_ENV" | |
| - name: Prepare Vertex AI credentials | |
| if: steps.gate.outputs.provider_mode == 'vertex_ai' | |
| env: | |
| GCP_SA_KEY_JSON: ${{ secrets.GCP_SA_KEY }} | |
| run: | | |
| umask 077 | |
| credentials_file="$RUNNER_TEMP/gcp-sa-key.json" | |
| printf '%s' "$GCP_SA_KEY_JSON" > "$credentials_file" | |
| python3 - "$credentials_file" >> "$GITHUB_ENV" <<'PY' | |
| import json | |
| import pathlib | |
| import sys | |
| credentials_path = pathlib.Path(sys.argv[1]) | |
| def reject_duplicate_json_keys(pairs): | |
| parsed = {} | |
| for key, value in pairs: | |
| if key in parsed: | |
| raise ValueError("duplicate credential key") | |
| parsed[key] = value | |
| return parsed | |
| try: | |
| credentials_text = credentials_path.read_text(encoding="utf-8") | |
| credentials = json.loads( | |
| credentials_text, | |
| object_pairs_hook=reject_duplicate_json_keys, | |
| ) | |
| except (OSError, UnicodeDecodeError, json.JSONDecodeError, ValueError): | |
| raise SystemExit( | |
| "GCP_SA_KEY must be valid service account JSON for Vertex AI Strix scans." | |
| ) | |
| if not isinstance(credentials, dict): | |
| raise SystemExit( | |
| "GCP_SA_KEY must be a JSON object for Vertex AI Strix scans." | |
| ) | |
| project_id = str(credentials.get("project_id", "")).strip() | |
| if not project_id: | |
| raise SystemExit("GCP_SA_KEY must include project_id for Vertex AI Strix scans.") | |
| print(f"GOOGLE_APPLICATION_CREDENTIALS={credentials_path}") | |
| print(f"CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE={credentials_path}") | |
| print(f"VERTEXAI_PROJECT={project_id}") | |
| print(f"GOOGLE_CLOUD_PROJECT={project_id}") | |
| print(f"GCP_PROJECT={project_id}") | |
| print(f"GCLOUD_PROJECT={project_id}") | |
| print(f"CLOUDSDK_CORE_PROJECT={project_id}") | |
| print(f"CLOUDSDK_PROJECT={project_id}") | |
| PY | |
| - name: Prepare Strix model input file | |
| if: steps.gate.outputs.enabled == 'true' | |
| env: | |
| STRIX_MODEL: ${{ github.event.inputs.strix_llm || 'openai/gpt-5' }} | |
| run: | | |
| umask 077 | |
| strix_llm_file="$RUNNER_TEMP/strix_llm.txt" | |
| strix_model="$(printf '%s' "$STRIX_MODEL" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" | |
| case "$strix_model" in | |
| openai/gpt-5-mini* | openai/gpt-5-nano* | \ | |
| openai/openai/gpt-5-mini* | openai/openai/gpt-5-nano* | \ | |
| github_models/openai/gpt-5-mini* | github_models/openai/gpt-5-nano*) | |
| echo '::error::STRIX_LLM must not select mini or nano GPT-5 variants for security evidence.' | |
| exit 1 | |
| ;; | |
| openai/gpt-5* | openai/gpt-[6-9]* | openai/gpt-[1-9][0-9]* | \ | |
| openai/openai/gpt-5* | openai/openai/gpt-[6-9]* | openai/openai/gpt-[1-9][0-9]* | \ | |
| github_models/openai/gpt-5* | github_models/openai/gpt-[6-9]* | github_models/openai/gpt-[1-9][0-9]*) | |
| printf '%s' "${strix_model#github_models/}" > "$strix_llm_file" | |
| ;; | |
| openai/*) | |
| printf '%s' "$strix_model" > "$strix_llm_file" | |
| ;; | |
| openai-direct/gpt-*) | |
| printf 'openai_direct/%s' "${strix_model#openai-direct/}" > "$strix_llm_file" | |
| ;; | |
| gpt-*) | |
| printf 'openai_direct/%s' "$strix_model" > "$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, or an approved organization Vertex AI model.' | |
| exit 1 | |
| ;; | |
| esac | |
| echo "STRIX_LLM_FILE=$strix_llm_file" >> "$GITHUB_ENV" | |
| - name: Run Strix (quick) | |
| if: steps.gate.outputs.enabled == 'true' | |
| timeout-minutes: 30 | |
| # Security invariant for pull_request_target: execute only from the | |
| # trusted base checkout. The gate copies PR-head blobs into an isolated | |
| # temporary scope with execute bits stripped, then scans that scope as | |
| # data. PR evidence uses the __PR_SCOPE__ sentinel so the scanner target | |
| # cannot accidentally remain the trusted base checkout. | |
| working-directory: ${{ runner.temp }}/trusted-workspace | |
| env: | |
| STRIX_LLM_FILE: ${{ env.STRIX_LLM_FILE }} | |
| STRIX_REPO_ROOT: ${{ runner.temp }}/trusted-workspace | |
| LLM_API_BASE_FILE: ${{ env.LLM_API_BASE_FILE }} | |
| STRIX_LLM_DEFAULT_PROVIDER: ${{ steps.gate.outputs.provider_mode == 'vertex_ai' && 'vertex_ai' || 'openai' }} | |
| LLM_API_KEY_FILE: ${{ env.LLM_API_KEY_FILE }} | |
| GOOGLE_APPLICATION_CREDENTIALS: ${{ env.GOOGLE_APPLICATION_CREDENTIALS }} | |
| CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE: ${{ env.CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE }} | |
| VERTEXAI_PROJECT: ${{ env.VERTEXAI_PROJECT }} | |
| GOOGLE_CLOUD_PROJECT: ${{ env.GOOGLE_CLOUD_PROJECT }} | |
| GCP_PROJECT: ${{ env.GCP_PROJECT }} | |
| GCLOUD_PROJECT: ${{ env.GCLOUD_PROJECT }} | |
| CLOUDSDK_CORE_PROJECT: ${{ env.CLOUDSDK_CORE_PROJECT }} | |
| CLOUDSDK_PROJECT: ${{ env.CLOUDSDK_PROJECT }} | |
| VERTEXAI_LOCATION: ${{ secrets.VERTEX_LOCATION || 'us-central1' }} | |
| VERTEX_LOCATION: ${{ secrets.VERTEX_LOCATION || 'us-central1' }} | |
| STRIX_TARGET_PATH: ${{ (github.event_name == 'pull_request_target' || github.event.inputs.pr_number != '') && '__PR_SCOPE__' || './' }} | |
| STRIX_SOURCE_DIRS: ". backend frontend" | |
| STRIX_REASONING_EFFORT: low | |
| 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/deepseek/deepseek-v3-0324 github_models/deepseek/deepseek-r1-0528' || '' }} | |
| STRIX_FAIL_ON_PROVIDER_SIGNAL: "1" | |
| STRIX_VERTEX_FALLBACK_MODELS: "" | |
| NPM_CONFIG_IGNORE_SCRIPTS: "true" | |
| PNPM_CONFIG_IGNORE_SCRIPTS: "true" | |
| YARN_ENABLE_SCRIPTS: "false" | |
| BUN_CONFIG_IGNORE_SCRIPTS: "true" | |
| STRIX_FAIL_ON_MIN_SEVERITY: MEDIUM | |
| STRIX_DISABLE_PR_SCOPING: ${{ (github.event_name == 'pull_request_target' || github.event.inputs.pr_number != '') && '0' || '1' }} | |
| GH_TOKEN: ${{ (github.event_name == 'pull_request_target' || github.event.inputs.pr_number != '') && github.token || '' }} | |
| PR_NUMBER: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event.inputs.pr_number }} | |
| PR_BASE_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }} | |
| PR_HEAD_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} | |
| IS_PR_EVIDENCE_RUN: ${{ (github.event_name == 'pull_request_target' || github.event.inputs.pr_number != '') && 'true' || 'false' }} | |
| run: | | |
| budget_suffix="TIME""OUT" | |
| process_budget_seconds="1500" | |
| export "LLM_${budget_suffix}=120" | |
| export "STRIX_MEMORY_COMPRESSOR_${budget_suffix}=10" | |
| export "STRIX_PROCESS_${budget_suffix}_SECONDS=$process_budget_seconds" | |
| export "STRIX_TOTAL_${budget_suffix}_SECONDS=1800" | |
| bash "$TRUSTED_STRIX_GATE" | |
| - name: Collect Strix reports for artifact upload | |
| if: ${{ always() && steps.gate.outputs.enabled == 'true' }} | |
| env: | |
| PR_HEAD_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$GITHUB_WORKSPACE/strix_runs" | |
| copied_reports=0 | |
| for candidate_dir in "$TRUSTED_WORKSPACE/strix_runs" "$RUNNER_TEMP/strix_runs"; do | |
| if [ -d "$candidate_dir" ] && [ -n "$(find "$candidate_dir" -mindepth 1 -print -quit)" ]; then | |
| cp -R "$candidate_dir"/. "$GITHUB_WORKSPACE/strix_runs"/ | |
| copied_reports=1 | |
| fi | |
| done | |
| if [ -n "$(find "$GITHUB_WORKSPACE/strix_runs" -mindepth 1 -print -quit)" ]; then | |
| copied_reports=1 | |
| fi | |
| if [ "$copied_reports" -eq 0 ]; then | |
| summary_head_sha="${PR_HEAD_SHA:-$GITHUB_SHA}" | |
| { | |
| echo "Strix scan completed without structured report files." | |
| echo "run_id=$GITHUB_RUN_ID" | |
| echo "head_sha=$summary_head_sha" | |
| } > "$GITHUB_WORKSPACE/strix_runs/scan-summary.txt" | |
| fi | |
| - name: Upload Strix reports artifact | |
| if: ${{ always() && steps.gate.outputs.enabled == 'true' }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: strix-reports | |
| path: strix_runs/ | |
| if-no-files-found: error | |
| retention-days: 5 | |
| publish-manual-pr-evidence-status: | |
| name: publish-manual-pr-evidence-status | |
| needs: strix | |
| if: ${{ always() && !cancelled() && github.event_name == 'workflow_dispatch' && github.event.inputs.pr_head_sha != '' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| statuses: write | |
| steps: | |
| - name: Publish same-head manual Strix status | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PR_HEAD_SHA: ${{ github.event.inputs.pr_head_sha }} | |
| STRIX_RESULT: ${{ needs.strix.result }} | |
| run: | | |
| set -euo pipefail | |
| if ! [[ "$PR_HEAD_SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then | |
| echo "::error::PR head SHA must be a 40-character git SHA." | |
| exit 1 | |
| fi | |
| case "$STRIX_RESULT" in | |
| success) | |
| state="success" | |
| description="Manual workflow_dispatch Strix evidence passed" | |
| ;; | |
| failure|cancelled|skipped) | |
| state="failure" | |
| description="Manual workflow_dispatch Strix evidence failed" | |
| ;; | |
| *) | |
| state="error" | |
| description="Manual workflow_dispatch Strix evidence inconclusive" | |
| ;; | |
| esac | |
| gh api -X POST "repos/${GITHUB_REPOSITORY}/statuses/${PR_HEAD_SHA}" \ | |
| -f state="$state" \ | |
| -f context="strix" \ | |
| -f description="$description" \ | |
| -f target_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" |