From d8e0d0086eab2cb77865af08e1e27b0efb9a3907 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 17 Jul 2026 01:41:30 +0900 Subject: [PATCH] fix(strix): normalize executable permissions --- .github/workflows/strix.yml | 10 +++++++--- scripts/ci/test_strix_quick_gate.sh | 2 ++ tests/test_required_workflow_queue_contract.py | 12 ++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index 177a31aa..5f9f7ff5 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -485,6 +485,7 @@ jobs: working-directory: trusted-strix-source run: | set -euo pipefail + umask 022 python3 -m pip install --disable-pip-version-check --no-cache-dir --require-hashes -r requirements-strix-ci-hashes.txt strix_executable="$(command -v strix || true)" if [ -z "$strix_executable" ] || [[ "$strix_executable" != /* ]] \ @@ -499,6 +500,7 @@ jobs: exit 1 ;; esac + chmod go-w -- "$strix_executable" strix_scripts_root="$(python3 -c 'import sysconfig; print(sysconfig.get_path("scripts"))')" strix_executable_sha256="$(python3 - "$strix_executable" <<'PY' import hashlib @@ -508,9 +510,11 @@ jobs: print(hashlib.sha256(Path(sys.argv[1]).read_bytes()).hexdigest()) PY )" - printf 'STRIX_EXECUTABLE_PATH=%s\n' "$strix_executable" >> "$GITHUB_ENV" - printf 'STRIX_EXECUTABLE_ROOT=%s\n' "$strix_scripts_root" >> "$GITHUB_ENV" - printf 'STRIX_EXECUTABLE_SHA256=%s\n' "$strix_executable_sha256" >> "$GITHUB_ENV" + { + printf 'STRIX_EXECUTABLE_PATH=%s\n' "$strix_executable" + printf 'STRIX_EXECUTABLE_ROOT=%s\n' "$strix_scripts_root" + printf 'STRIX_EXECUTABLE_SHA256=%s\n' "$strix_executable_sha256" + } >> "$GITHUB_ENV" - name: Mask LLM API key if: steps.gate.outputs.enabled == 'true' diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index a8e62d9d..34e9b81d 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -889,6 +889,8 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'STRIX_EXECUTABLE_PATH=%s' "Strix workflow captures the pinned installation executable before scanning" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'STRIX_EXECUTABLE_SHA256=%s' "Strix workflow pins the installed executable digest before scanning" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'STRIX_EXECUTABLE_ROOT=%s' "Strix workflow pins the installed executable root before scanning" + assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'umask 022' "Strix installation creates runner tools without group/world write permissions" + assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'chmod go-w -- "$strix_executable"' "Strix installation normalizes the 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" '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" diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 384750c8..50d57044 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -191,6 +191,18 @@ def test_strix_cancels_superseded_pr_head_security_evidence() -> None: ) +def test_strix_install_normalizes_executable_permissions_before_hashing() -> None: + workflow = workflow_text("strix.yml") + install_step = workflow_step(workflow, "Install Strix") + + assert install_step.index("umask 022") < install_step.index( + "python3 -m pip install" + ) + assert install_step.index('chmod go-w -- "$strix_executable"') < install_step.index( + 'strix_executable_sha256="' + ) + + def test_pull_request_close_events_cancel_superseded_runs_without_heavy_jobs() -> None: workflows = ( "close-empty-pr.yml",