Skip to content
127 changes: 97 additions & 30 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ on:
# 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

concurrency:
group: strix-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand Down Expand Up @@ -38,14 +51,12 @@ jobs:
run: |
set -euo pipefail
trusted_workspace="$RUNNER_TEMP/trusted-workspace"
trusted_archive="$RUNNER_TEMP/trusted-workspace.tar.gz"
mkdir -p "$trusted_workspace"
gh api "/repos/${REPOSITORY}/tarball/${TRUSTED_WORKSPACE_SHA}" > "$trusted_archive"
tar -xzf "$trusted_archive" -C "$trusted_workspace" --strip-components=1
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 --prune origin "$TRUSTED_WORKSPACE_SHA"
git -C "$trusted_workspace" fetch --no-tags --depth=1 origin "$TRUSTED_WORKSPACE_SHA"
git -C "$trusted_workspace" checkout --detach --quiet "$TRUSTED_WORKSPACE_SHA"
git -C "$trusted_workspace" cat-file -e "$TRUSTED_WORKSPACE_SHA^{commit}"
{
echo "TRUSTED_WORKSPACE=$trusted_workspace"
Expand All @@ -54,11 +65,15 @@ jobs:
} >> "$GITHUB_ENV"

- name: Fetch pull request head for trusted scan
if: github.event_name == 'pull_request_target'
if: github.event_name == 'pull_request_target' || github.event.inputs.pr_number != ''
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_NUMBER: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event.inputs.pr_number }}
PR_HEAD_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }}
run: |
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
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")"
Expand All @@ -80,27 +95,38 @@ jobs:
- name: Gate Strix secrets
id: gate
env:
STRIX_OPENAI_MODEL: ${{ secrets.STRIX_LLM || 'openai/gpt-5.4' }}
STRIX_MODEL: ${{ secrets.STRIX_LLM || 'vertex_ai/gemini-2.5-flash' }}
STRIX_OPENAI_API_KEY: ${{ secrets.STRIX_OPENAI_API_KEY }}
STRIX_VERTEX_CREDENTIALS: ${{ secrets.GCP_SA_KEY }}
run: |
strix_model="$(printf '%s' "$STRIX_OPENAI_MODEL" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
strix_model="$(printf '%s' "$STRIX_MODEL" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
case "$strix_model" in
gpt-5.[4-9]* | gpt-5.[1-9][0-9]* | gpt-[6-9]* | gpt-[1-9][0-9]* | \
openai/gpt-5.[4-9]* | openai/gpt-5.[1-9][0-9]* | openai/gpt-[6-9]* | openai/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 an OpenAI Platform GPT-5.4 or newer model, for example gpt-5.4 or openai/gpt-5.4.'
echo '::error::STRIX_LLM must select direct OpenAI GPT-5.4 or newer, or an approved organization Vertex AI model.'
exit 1
;;
esac
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
echo 'provider_mode=openai_direct' >> "$GITHUB_OUTPUT"

- name: Install Strix
if: steps.gate.outputs.enabled == 'true'
Expand All @@ -112,7 +138,7 @@ jobs:
- name: Mask LLM API key
if: steps.gate.outputs.enabled == 'true'
env:
LLM_API_KEY: ${{ secrets.STRIX_OPENAI_API_KEY }}
LLM_API_KEY: ${{ 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.
Expand All @@ -128,11 +154,11 @@ jobs:
- name: Prepare LLM API key input file
if: steps.gate.outputs.enabled == 'true'
env:
LLM_API_KEY_SECRET: ${{ secrets.STRIX_OPENAI_API_KEY }}
LLM_API_KEY_SECRET: ${{ steps.gate.outputs.provider_mode == 'openai_direct' && secrets.STRIX_OPENAI_API_KEY || '' }}
run: |
sanitized="$(printf '%s' "$LLM_API_KEY_SECRET" | tr -d '\r\n')"
trimmed="$(printf '%s' "$sanitized" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
if [ -z "$trimmed" ]; then
if [ -z "$trimmed" ] && [ "${{ steps.gate.outputs.provider_mode }}" = "openai_direct" ]; then
echo '::error::STRIX_OPENAI_API_KEY is required for Strix OpenAI Platform scans.'
exit 1
fi
Expand All @@ -141,23 +167,54 @@ jobs:
printf '%s' "$sanitized" > "$llm_api_key_file"
echo "LLM_API_KEY_FILE=$llm_api_key_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])
credentials = json.loads(credentials_path.read_text())
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_OPENAI_MODEL: ${{ secrets.STRIX_LLM || 'openai/gpt-5.4' }}
STRIX_MODEL: ${{ secrets.STRIX_LLM || 'vertex_ai/gemini-2.5-flash' }}
run: |
umask 077
strix_llm_file="$RUNNER_TEMP/strix_llm.txt"
strix_model="$(printf '%s' "$STRIX_OPENAI_MODEL" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
strix_model="$(printf '%s' "$STRIX_MODEL" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
case "$strix_model" in
openai/*)
printf '%s' "$strix_model" > "$strix_llm_file"
;;
gpt-*)
printf 'openai/%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 an OpenAI Platform GPT-5.4 or newer model, for example gpt-5.4 or openai/gpt-5.4.'
echo '::error::STRIX_LLM must select direct OpenAI GPT-5.4 or newer, or an approved organization Vertex AI model.'
exit 1
;;
esac
Expand All @@ -167,8 +224,18 @@ jobs:
if: steps.gate.outputs.enabled == 'true'
env:
STRIX_LLM_FILE: ${{ env.STRIX_LLM_FILE }}
STRIX_LLM_DEFAULT_PROVIDER: openai
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: ./
STRIX_SOURCE_DIRS: .
STRIX_REASONING_EFFORT: low
Expand All @@ -177,15 +244,15 @@ jobs:
STRIX_MEMORY_COMPRESSOR_TIMEOUT: 10
STRIX_TRANSIENT_RETRY_PER_MODEL: 2
STRIX_TRANSIENT_RETRY_BACKOFF_SECONDS: 3
STRIX_PROCESS_TIMEOUT_SECONDS: ${{ github.event_name == 'pull_request_target' && '1200' || '2400' }}
STRIX_PROCESS_TIMEOUT_SECONDS: ${{ (github.event_name == 'pull_request_target' || github.event.inputs.pr_number != '') && '1200' || '2400' }}
STRIX_TOTAL_TIMEOUT_SECONDS: 4800
STRIX_PR_SCOPE_MAX_FILES_PER_BATCH: 12
STRIX_FAIL_ON_MIN_SEVERITY: MEDIUM
STRIX_DISABLE_PR_SCOPING: ${{ github.event_name == 'pull_request_target' && '0' || '1' }}
GH_TOKEN: ${{ github.event_name == 'pull_request_target' && github.token || '' }}
PR_NUMBER: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || '' }}
PR_BASE_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || '' }}
PR_HEAD_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }}
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 }}
run: |
cd "$TRUSTED_WORKSPACE"
bash "$TRUSTED_STRIX_GATE"
Expand Down
25 changes: 16 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@
explicit `if: ${{ always() }}` upload steps when needed.
- Prefer upgrading or removing vulnerable dependencies over downgrading patched
packages unless compatibility evidence is recorded in the PR.
- Strix Security Scan must use an explicitly named `STRIX_OPENAI_API_KEY`
OpenAI Platform credential with an OpenAI GPT-5.4-or-newer model. Do not route
Strix through GitHub Models, `github.token`, generic `LLM_API_KEY`, Gemini,
Google/Vertex, GPT-4o, or GPT-4.1; record direct-provider evidence in the PR.
Direct OpenAI model names may be plain (`gpt-5.4`) or provider-qualified
(`openai/gpt-5.4`), but must never use the GitHub Models
`openai/openai/...` prefix.
Keep architecture docs and reusable Strix gate tests aligned with this rule so
stale GitHub Models examples cannot re-enter copied workflow guidance.
- Strix Security Scan must not route through GitHub Models, `github.token`,
generic `LLM_API_KEY`, GPT-4o, or GPT-4.1. The current organization-secret
route is `STRIX_LLM` with `GCP_SA_KEY`; `vertex_ai/gemini-2.5-flash` is the
validated operational model after run `26581416713` proved
`vertex_ai/gemini-3.1-pro-preview-customtools` returns Vertex 404/no-access in
this project. Expose Google/Vertex credentials only for Vertex provider mode.
Direct OpenAI GPT-5.4-or-newer scans remain supported only when selected
explicitly with `STRIX_OPENAI_API_KEY`. Do not silently fall back between
providers, and record provider evidence in the PR. Keep architecture docs and
reusable Strix gate tests aligned with this rule so stale GitHub Models,
OpenAI-only, unavailable-model, or generic-key examples cannot re-enter copied
workflow guidance.

## PR automation and review defaults

Expand Down Expand Up @@ -115,6 +118,10 @@
avoid single-token columns such as `id`, `title`, `status`, or `priority` on
newly introduced objects.
- When reviews find public/private identifier leaks, stale API fixture shapes, or recurring bug patterns, update tests, frontend mocks, E2E mocks, README examples, architecture docs, and explicitly record the anti-pattern in `AGENTS.md` so the same bug pattern does not reappear in copied examples.
- When robot review cites an obsolete Strix provider policy, update the docs and
tests to the current secret contract before accepting a rollback suggestion;
do not reintroduce generic `LLM_API_KEY`, GitHub Models, or cross-provider
credential forwarding while trying to satisfy old comments.
- When reviews find inert navigation/dead-space controls, either wire them to an
implemented workspace route/API or remove the control; do not leave
high-traffic drawer/sidebar entries as permanent `준비 중` copy.
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ mail/calendar/file systems.
open-source observability.
- PR automation is metadata-only and uses current-head robot-review evidence plus
required checks. Human approval is not awaited by default under repo policy.
- Strix PR/security evidence requires an OpenAI GPT-5.4-or-newer model through
an explicit `STRIX_OPENAI_API_KEY` OpenAI Platform credential. The workflow
fails closed rather than falling back to GitHub Models, Google/Vertex/Gemini,
`github.token`, or GPT-4-era models. Pending CodeRabbit or check evidence is
a wait state, not a hard blocker.
- Strix PR/security evidence uses the organization-secret provider selected by
`STRIX_LLM` with `GCP_SA_KEY`. The validated operational route is
`vertex_ai/gemini-2.5-flash`; direct OpenAI GPT-5.4-or-newer remains
supported only with an explicit `STRIX_OPENAI_API_KEY`. The workflow fails
closed rather than falling back to GitHub Models, `github.token`, generic
`LLM_API_KEY`, or GPT-4-era models. Pending CodeRabbit or check evidence is a
wait state, not a hard blocker.
- Security governance is source-backed through signed
`/api/security/access-surface`. The endpoint reads scoped WebDAV, CalDAV, and
connector evidence, reuses the deny-first RBAC/ABAC policy engine, and returns
Expand Down
9 changes: 5 additions & 4 deletions docs/plans/2026-05-19-north-star-gap-closure.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ connector, and PR governance is metadata-only.
the exact `Strix Security Scan` workflow, runs a trusted-base governance script,
separates pending/waiting states from failures, and updates an idempotent marker
comment instead of posting duplicates.
- [x] Strix GPT-5 hardening: the workflow keeps GPT-5.4-or-newer as the model
requirement and uses only an explicitly named `STRIX_OPENAI_API_KEY` OpenAI
Platform credential. It fails closed instead of routing scanner traffic
through GitHub Models, `github.token`, Gemini, GPT-4o, or GPT-4.1.
- [x] Strix provider hardening: the workflow fails closed instead of routing
scanner traffic through GitHub Models, `github.token`, generic `LLM_API_KEY`,
GPT-4o, or GPT-4.1. The current approved default is the validated
org-secret Vertex AI model, while direct OpenAI GPT-5.4+ remains
explicit-only through `STRIX_OPENAI_API_KEY`.
- [x] CalDAV source registry: `/api/calendar/writeback-intent` now resolves
DB-backed `calendar_writeback_sources` rows with opaque `source_uid` values
instead of exposing sequential CalDAV account ids or accepting browser-supplied
Expand Down
5 changes: 3 additions & 2 deletions docs/plans/2026-05-27-caldav-writeback-source-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ client/control plane, not a calendar host.
target any row.
- Return intent metadata only: protocol, provider, ETag/If-Match requirement,
provenance, and audit event. No provider write is executed in this slice.
- Keep Strix on direct OpenAI Platform credentials only; no GitHub Models path is
part of this work.
- Keep Strix on the current provider governance contract: validated org-secret
Vertex AI by default, explicit direct OpenAI only when selected, and no GitHub
Models path as part of this work.

## Verification

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ Browser evidence to inspect:

## Governance

- Strix remains OpenAI Platform direct-only through `STRIX_OPENAI_API_KEY`.
- GitHub Models, `github.token`, generic `LLM_API_KEY`, Gemini/Vertex fallback,
GPT-4o, and GPT-4.1 are not valid Strix routes.
- Strix follows the current provider governance contract: the approved default
is the validated org-secret Vertex AI model `vertex_ai/gemini-2.5-flash`,
while direct OpenAI GPT-5.4+ remains explicit-only through
`STRIX_OPENAI_API_KEY`.
- GitHub Models, `github.token`, generic `LLM_API_KEY`, arbitrary
Gemini/Vertex fallback, GPT-4o, and GPT-4.1 are not valid Strix routes.
Loading
Loading