From d71249b3ae1fda5875343cef83953c7138b19e0d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 26 Jun 2026 18:35:30 +0900 Subject: [PATCH 1/2] Run Strix from central workflow source --- .github/workflows/strix.yml | 52 ++++++++++++++++++++++++----- scripts/ci/strix_quick_gate.sh | 4 ++- scripts/ci/test_strix_quick_gate.sh | 13 ++++++-- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index db016dcf1..2a1112423 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -62,11 +62,46 @@ jobs: with: python-version: "3.13" - - name: Materialize trusted workspace + - name: Resolve trusted Strix source ref + id: trusted_source + env: + WORKFLOW_REF: ${{ github.workflow_ref }} + run: | + set -euo pipefail + trusted_ref="main" + case "$WORKFLOW_REF" in + ContextualWisdomLab/.github/.github/workflows/strix.yml@*) + trusted_ref="${WORKFLOW_REF##*@}" + ;; + esac + printf 'ref=%s\n' "$trusted_ref" >>"$GITHUB_OUTPUT" + + - name: Checkout trusted Strix source + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: ContextualWisdomLab/.github + 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" + { + 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" + } >> "$GITHUB_ENV" + + - name: Materialize target workspace env: GH_TOKEN: ${{ github.token }} REPOSITORY: ${{ github.repository }} - TRUSTED_WORKSPACE_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || github.sha }} + 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" @@ -74,13 +109,11 @@ jobs: 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 "$TRUSTED_WORKSPACE_SHA" - git -C "$trusted_workspace" checkout --detach --quiet "$TRUSTED_WORKSPACE_SHA" - git -C "$trusted_workspace" cat-file -e "$TRUSTED_WORKSPACE_SHA^{commit}" + 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" - echo "TRUSTED_STRIX_GATE=$trusted_workspace/scripts/ci/strix_quick_gate.sh" - echo "TRUSTED_STRIX_GATE_TEST=$trusted_workspace/scripts/ci/test_strix_quick_gate.sh" } >> "$GITHUB_ENV" - name: Fetch pull request head for trusted scan @@ -145,7 +178,7 @@ jobs: - name: Self-test Strix gate script timeout-minutes: 10 - working-directory: ${{ runner.temp }}/trusted-workspace + working-directory: trusted-strix-source run: | set -euo pipefail printf 'Running Strix gate self-test with a 10-minute step timeout.\n' @@ -214,7 +247,7 @@ jobs: - name: Install Strix if: steps.gate.outputs.enabled == 'true' - working-directory: ${{ runner.temp }}/trusted-workspace + 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 @@ -363,6 +396,7 @@ jobs: 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 }} diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 4a9b306c5..60463d32a 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -10,7 +10,9 @@ set -euo pipefail SCRIPT_DIR="$({ CDPATH='' && cd -P -- "$(dirname -- "$0")" && pwd -P; })" -REPO_ROOT="$({ CDPATH='' && cd -P -- "$SCRIPT_DIR/../.." && pwd -P; })" +DEFAULT_REPO_ROOT="$({ CDPATH='' && cd -P -- "$SCRIPT_DIR/../.." && pwd -P; })" +RAW_REPO_ROOT="${STRIX_REPO_ROOT:-$DEFAULT_REPO_ROOT}" +REPO_ROOT="$({ CDPATH='' && cd -P -- "$RAW_REPO_ROOT" && pwd -P; })" RAW_TARGET_PATH="${STRIX_TARGET_PATH:-./}" TARGET_PATH="" PR_SCOPE_TARGET_SENTINEL="__PR_SCOPE__" diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index b88533cfd..ea107282c 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -105,16 +105,23 @@ assert_strix_workflow_pr_trigger_hardened() { assert_file_contains "$workflow_file" "models: read" "strix workflow grants only the GitHub Models read permission needed for Strix" assert_file_contains "$workflow_file" "actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6" "strix workflow pins actions/setup-python" assert_file_contains "$workflow_file" 'python-version: "3.13"' "strix workflow runs Python steps on Python 3.13" - assert_file_contains "$workflow_file" "Materialize trusted workspace" "strix workflow materializes trusted workspace" - assert_file_contains "$workflow_file" "TRUSTED_WORKSPACE_SHA" "strix workflow pins trusted workspace SHA" + assert_file_contains "$workflow_file" "Resolve trusted Strix source ref" "strix workflow resolves the central trusted Strix source ref" + assert_file_contains "$workflow_file" "github.workflow_ref" "strix workflow derives the trusted source ref from the required-workflow source" + assert_file_contains "$workflow_file" "Checkout trusted Strix source" "strix workflow checks out the central Strix source" + assert_file_contains "$workflow_file" "repository: ContextualWisdomLab/.github" "strix workflow checks out central Strix scripts instead of target-repo copies" + assert_file_contains "$workflow_file" 'TRUSTED_STRIX_SOURCE=$trusted_strix_source' "strix workflow exports the central Strix source path" + assert_file_contains "$workflow_file" 'TRUSTED_STRIX_GATE=$trusted_strix_source/scripts/ci/strix_quick_gate.sh' "strix workflow executes the central Strix gate script" + assert_file_contains "$workflow_file" "Materialize target workspace" "strix workflow materializes target repository data separately from trusted scripts" + assert_file_contains "$workflow_file" "TARGET_WORKSPACE_SHA" "strix workflow pins target workspace SHA" assert_file_contains "$workflow_file" "TRUSTED_WORKSPACE=\$trusted_workspace" "strix workflow exports a trusted workspace path" assert_file_contains "$workflow_file" "git -C \"\$TRUSTED_WORKSPACE\"" "strix workflow runs git only inside trusted workspace" assert_file_contains "$workflow_file" 'working-directory: ${{ runner.temp }}/trusted-workspace' "strix workflow executes privileged steps from the trusted workspace" + assert_file_contains "$workflow_file" "STRIX_REPO_ROOT:" "strix workflow passes target repository root to the central Strix gate" assert_file_contains "$workflow_file" "bash \"\$TRUSTED_STRIX_GATE_TEST\"" "strix workflow self-test executes trusted temp script" assert_file_contains "$workflow_file" "bash \"\$TRUSTED_STRIX_GATE\"" "strix workflow executes trusted temp gate script" assert_file_contains "$workflow_file" "Collect Strix reports for artifact upload" "strix workflow preserves reports from trusted workspace" assert_file_contains "$workflow_file" "scan-summary.txt" "strix workflow creates a fallback artifact when Strix emits no report files" - assert_file_not_contains "$workflow_file" "actions/checkout" "strix workflow avoids checkout in privileged context" + assert_file_not_contains "$workflow_file" 'repository: ${{ github.repository }}' "strix workflow must not checkout target repository code with actions/checkout in privileged context" assert_file_not_contains "$workflow_file" "run: bash ./scripts/ci/test_strix_quick_gate.sh" "strix workflow avoids direct repo self-test execution on privileged trigger" assert_file_not_contains "$workflow_file" "run: bash ./scripts/ci/strix_quick_gate.sh" "strix workflow avoids direct repo gate execution on privileged trigger" assert_file_contains "$workflow_file" "Fetch pull request head for trusted scan" "strix workflow fetches PR head without checkout" From 4872a88b4f9d51208dde91032de3d5a0f230889e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 26 Jun 2026 18:51:30 +0900 Subject: [PATCH 2/2] Harden central Strix source selection --- .github/workflows/strix.yml | 51 ++++++++++++++++++++++++----- scripts/ci/strix_quick_gate.sh | 4 +++ scripts/ci/test_strix_quick_gate.sh | 11 +++++-- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index 2a1112423..2c6291f11 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -65,21 +65,54 @@ jobs: - name: Resolve trusted Strix source ref id: trusted_source env: - WORKFLOW_REF: ${{ github.workflow_ref }} + JOB_CONTEXT_JSON: ${{ toJSON(job) }} + GITHUB_CONTEXT_JSON: ${{ toJSON(github) }} run: | set -euo pipefail - trusted_ref="main" - case "$WORKFLOW_REF" in - ContextualWisdomLab/.github/.github/workflows/strix.yml@*) - trusted_ref="${WORKFLOW_REF##*@}" - ;; - esac - printf 'ref=%s\n' "$trusted_ref" >>"$GITHUB_OUTPUT" + 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: ContextualWisdomLab/.github + repository: ${{ steps.trusted_source.outputs.repository }} fetch-depth: 1 persist-credentials: false ref: ${{ steps.trusted_source.outputs.ref }} diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 60463d32a..d8bb316db 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -12,6 +12,10 @@ set -euo pipefail SCRIPT_DIR="$({ CDPATH='' && cd -P -- "$(dirname -- "$0")" && pwd -P; })" DEFAULT_REPO_ROOT="$({ CDPATH='' && cd -P -- "$SCRIPT_DIR/../.." && pwd -P; })" RAW_REPO_ROOT="${STRIX_REPO_ROOT:-$DEFAULT_REPO_ROOT}" +if [ -z "$RAW_REPO_ROOT" ] || [ ! -d "$RAW_REPO_ROOT" ] || [ -L "$RAW_REPO_ROOT" ]; then + echo "ERROR: STRIX_REPO_ROOT must reference a regular directory when provided." >&2 + exit 2 +fi REPO_ROOT="$({ CDPATH='' && cd -P -- "$RAW_REPO_ROOT" && pwd -P; })" RAW_TARGET_PATH="${STRIX_TARGET_PATH:-./}" TARGET_PATH="" diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index ea107282c..bec3f06c9 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -106,9 +106,13 @@ assert_strix_workflow_pr_trigger_hardened() { assert_file_contains "$workflow_file" "actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6" "strix workflow pins actions/setup-python" assert_file_contains "$workflow_file" 'python-version: "3.13"' "strix workflow runs Python steps on Python 3.13" assert_file_contains "$workflow_file" "Resolve trusted Strix source ref" "strix workflow resolves the central trusted Strix source ref" - assert_file_contains "$workflow_file" "github.workflow_ref" "strix workflow derives the trusted source ref from the required-workflow source" + assert_file_contains "$workflow_file" "toJSON(job)" "strix workflow derives the trusted source from the job workflow context" + assert_file_contains "$workflow_file" "workflow_repository" "strix workflow derives the trusted source repository from the job workflow identity" + assert_file_contains "$workflow_file" "workflow_sha" "strix workflow pins trusted source checkout to the job workflow commit SHA when available" + assert_file_contains "$workflow_file" "workflow_ref" "strix workflow falls back to the required-workflow source ref when the SHA is unavailable" assert_file_contains "$workflow_file" "Checkout trusted Strix source" "strix workflow checks out the central Strix source" - assert_file_contains "$workflow_file" "repository: ContextualWisdomLab/.github" "strix workflow checks out central Strix scripts instead of target-repo copies" + assert_file_contains "$workflow_file" 'repository: ${{ steps.trusted_source.outputs.repository }}' "strix workflow checks out central Strix scripts instead of target-repo copies" + assert_file_contains "$workflow_file" 'ref: ${{ steps.trusted_source.outputs.ref }}' "strix workflow checks out the exact trusted Strix source ref" assert_file_contains "$workflow_file" 'TRUSTED_STRIX_SOURCE=$trusted_strix_source' "strix workflow exports the central Strix source path" assert_file_contains "$workflow_file" 'TRUSTED_STRIX_GATE=$trusted_strix_source/scripts/ci/strix_quick_gate.sh' "strix workflow executes the central Strix gate script" assert_file_contains "$workflow_file" "Materialize target workspace" "strix workflow materializes target repository data separately from trusted scripts" @@ -121,6 +125,9 @@ assert_strix_workflow_pr_trigger_hardened() { assert_file_contains "$workflow_file" "bash \"\$TRUSTED_STRIX_GATE\"" "strix workflow executes trusted temp gate script" assert_file_contains "$workflow_file" "Collect Strix reports for artifact upload" "strix workflow preserves reports from trusted workspace" assert_file_contains "$workflow_file" "scan-summary.txt" "strix workflow creates a fallback artifact when Strix emits no report files" + local checkout_count + checkout_count="$(grep -Fc "uses: actions/checkout@" "$workflow_file")" + assert_equals "1" "$checkout_count" "strix workflow uses actions/checkout exactly once for the central trusted source" assert_file_not_contains "$workflow_file" 'repository: ${{ github.repository }}' "strix workflow must not checkout target repository code with actions/checkout in privileged context" assert_file_not_contains "$workflow_file" "run: bash ./scripts/ci/test_strix_quick_gate.sh" "strix workflow avoids direct repo self-test execution on privileged trigger" assert_file_not_contains "$workflow_file" "run: bash ./scripts/ci/strix_quick_gate.sh" "strix workflow avoids direct repo gate execution on privileged trigger"