From 5f09aabf457e8b930ddc615f3bf0e1e13968fed2 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 14 Sep 2026 09:46:11 -0700 Subject: [PATCH 1/4] Gate the self-hosted STT job away from fork PRs The `native-whisper` job in `stt-miri.yml` runs on a self-hosted Windows CUDA runner, and the workflow triggers on `pull_request`, so an approved fork PR would execute arbitrary test code on a persistent machine. The job now runs only on pushes, dispatches, and PRs whose head repository is this repository. - Fork PRs keep the `pure-stt-state` Miri jobs, which run on GitHub-hosted runners. --- .github/workflows/stt-miri.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/stt-miri.yml b/.github/workflows/stt-miri.yml index fb402c775..802d6675f 100644 --- a/.github/workflows/stt-miri.yml +++ b/.github/workflows/stt-miri.yml @@ -44,6 +44,10 @@ jobs: run: cargo +nightly-2026-09-05 miri test -p gateway-stt --features test-fixtures miri_ native-whisper: + # A self-hosted runner must never execute untrusted code: run this job + # on pushes, dispatches, and same-repository PRs only. Fork PRs still + # get the Miri jobs, which run on GitHub-hosted runners. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository runs-on: [self-hosted, windows, cuda] timeout-minutes: 90 steps: From df18e9cb1c56d813cd4ed9842441aac4f113c102 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 14 Sep 2026 09:46:45 -0700 Subject: [PATCH 2/4] Default the release workflow token to read-only `promptforge-gateway-v-release.yml` triggers on `pull_request` with `contents: write` at the top level, and its `plan` step interpolates `github.ref_name` directly into a run command. The workflow default is now `contents: read`, the `plan`, `host`, and `announce` jobs grant `contents: write` themselves, and the dist subcommand reaches the shell through `DIST_PLAN_COMMAND` instead of inline interpolation. - HAND-EDIT comments mark the permission change and the env indirection for re-application after `cargo dist generate`. - On `pull_request` the `plan` step still runs `dist plan`; only tag pushes receive the `host --steps=create` arguments. --- .../promptforge-gateway-v-release.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/promptforge-gateway-v-release.yml b/.github/workflows/promptforge-gateway-v-release.yml index db8840cff..496f2ce5c 100644 --- a/.github/workflows/promptforge-gateway-v-release.yml +++ b/.github/workflows/promptforge-gateway-v-release.yml @@ -14,8 +14,12 @@ # title/body based on your changelogs. name: Release +# HAND-EDIT (re-apply after `cargo dist generate`): default the token to +# read-only. This workflow triggers on pull_request, where a write token is +# never needed; the plan, host, and announce jobs grant contents: write +# themselves. permissions: - "contents": "write" + "contents": "read" # This task will run whenever you push a git tag that looks like a version # like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. @@ -48,6 +52,8 @@ jobs: # Run 'dist plan' (or host) to determine what tasks we need to do plan: runs-on: "ubuntu-22.04" + permissions: + "contents": "write" outputs: val: ${{ steps.plan.outputs.manifest }} tag: ${{ !github.event.pull_request && github.ref_name || '' }} @@ -55,6 +61,10 @@ jobs: publishing: ${{ !github.event.pull_request }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # HAND-EDIT (re-apply after `cargo dist generate`): the subcommand + # travels through the environment so github.ref_name is never + # interpolated into the run command. + DIST_PLAN_COMMAND: ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} steps: - uses: actions/checkout@v6 with: @@ -77,7 +87,8 @@ jobs: # but also really annoying to build CI around when it needs secrets to work right.) - id: plan run: | - dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json + # Intentionally unquoted: the env var carries the argument list. + dist $DIST_PLAN_COMMAND --output-format=json > plan-dist-manifest.json echo "dist ran successfully" cat plan-dist-manifest.json echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" @@ -227,6 +238,8 @@ jobs: - build-global-artifacts # Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine) if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} + permissions: + "contents": "write" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} runs-on: "ubuntu-22.04" @@ -288,6 +301,8 @@ jobs: # HAND-EDIT (re-apply after `cargo dist generate`): also require the # release-test gate, so a failed test means no release is created. if: ${{ always() && needs.host.result == 'success' && needs.custom-gateway-release-test.result == 'success' }} + permissions: + "contents": "write" runs-on: "ubuntu-22.04" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 6c997786eb54eef72659d0e352f81beca06c53f8 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 14 Sep 2026 09:48:22 -0700 Subject: [PATCH 3/4] Pin third-party actions to commit SHAs Third-party actions in the PR-triggered and dispatch workflows were referenced through mutable tags and branches, so a retagged release could change what CI executes. Each reference is now pinned to its current commit SHA with the original ref kept in a trailing comment. - GitHub-owned `actions/*` references stay on major-version tags. - `Swatinem/rust-cache` resolves to the signed `v2` tag (2.9.2); `tauri-apps/tauri-action` resolves to the unsigned `v0` tag. - `dtolnay/rust-toolchain` pins track the current heads of the `stable` and `nightly` branches and need periodic re-pinning. --- .github/workflows/ci.yml | 28 +++++++++---------- .github/workflows/llama-cuda-blackwell.yml | 10 +++---- .github/workflows/stt-miri.yml | 6 ++-- .github/workflows/whisper-lib.yml | 4 +-- .../workflows/workshop-installer-smoke.yml | 6 ++-- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 234e171ec..386a5fa9a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable with: components: rustfmt @@ -35,12 +35,12 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable with: components: clippy - name: Cache cargo - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Cache the embedding model uses: ./.github/actions/hf-model-cache @@ -83,13 +83,13 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable - name: Install cargo-nextest - uses: taiki-e/install-action@nextest + uses: taiki-e/install-action@4dc1969decfa71b34f25aa7f3dd4656654d9ad1e # nextest - name: Cache cargo - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Cache the embedding model uses: ./.github/actions/hf-model-cache @@ -132,10 +132,10 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable - name: Cache cargo - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Cache the embedding model uses: ./.github/actions/hf-model-cache @@ -159,15 +159,15 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable with: components: clippy - name: Install cargo-nextest - uses: taiki-e/install-action@nextest + uses: taiki-e/install-action@4dc1969decfa71b34f25aa7f3dd4656654d9ad1e # nextest - name: Cache cargo - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Cache the embedding model uses: ./.github/actions/hf-model-cache @@ -235,12 +235,12 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable with: components: clippy - name: Cache cargo - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Cache the embedding model uses: ./.github/actions/hf-model-cache @@ -342,7 +342,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: taiki-e/install-action@v2 + - uses: taiki-e/install-action@3f74d7c16a4242f1c95561e98edc25d36adb4375 # v2 with: tool: cargo-deny, cargo-audit diff --git a/.github/workflows/llama-cuda-blackwell.yml b/.github/workflows/llama-cuda-blackwell.yml index 561cf3c75..66f168c7c 100644 --- a/.github/workflows/llama-cuda-blackwell.yml +++ b/.github/workflows/llama-cuda-blackwell.yml @@ -31,10 +31,10 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable - name: Cache cargo - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Check out llama.cpp uses: actions/checkout@v4 @@ -92,10 +92,10 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable - name: Cache cargo - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 # The gateway's default features build the config UI with esbuild. - uses: actions/setup-node@v4 @@ -155,7 +155,7 @@ jobs: run: cp llama-server-cuda-blackwell-*.zip.sha256 SHA256SUMS - name: Publish the release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: tag_name: llama-cuda-blackwell-${{ env.TAG }} name: llama-cuda-blackwell-${{ env.TAG }} diff --git a/.github/workflows/stt-miri.yml b/.github/workflows/stt-miri.yml index 802d6675f..76f34db41 100644 --- a/.github/workflows/stt-miri.yml +++ b/.github/workflows/stt-miri.yml @@ -20,13 +20,13 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly + - uses: dtolnay/rust-toolchain@be39649afda95dbf70f87cce95f68b8d5797b296 # nightly with: toolchain: nightly-2026-09-05 components: miri - name: Cache Cargo and Miri - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Prepare Miri run: cargo +nightly-2026-09-05 miri setup @@ -54,7 +54,7 @@ jobs: - uses: actions/checkout@v4 - name: Cache Cargo - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Provision pinned native fixtures shell: powershell diff --git a/.github/workflows/whisper-lib.yml b/.github/workflows/whisper-lib.yml index 05117e320..eaffd0fb0 100644 --- a/.github/workflows/whisper-lib.yml +++ b/.github/workflows/whisper-lib.yml @@ -87,7 +87,7 @@ jobs: if: matrix.platform == 'macos-x86_64' run: >- cmake -S whisper.cpp -B build - -DBUILD_SHARED_LIBS=ON + -DBUILD_SHARED_LIBS=OFF -DGGML_METAL=OFF -DGGML_NATIVE=OFF -DWHISPER_BUILD_EXAMPLES=OFF @@ -239,7 +239,7 @@ jobs: run: sha256sum *.zip > SHA256SUMS - name: Publish the release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: tag_name: whisper-lib-${{ env.WHISPER_TAG }} name: whisper-lib-${{ env.WHISPER_TAG }} diff --git a/.github/workflows/workshop-installer-smoke.yml b/.github/workflows/workshop-installer-smoke.yml index 0e7b4fdc7..3acd3e952 100644 --- a/.github/workflows/workshop-installer-smoke.yml +++ b/.github/workflows/workshop-installer-smoke.yml @@ -21,10 +21,10 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable - name: Cache cargo - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Cache the embedding model uses: ./.github/actions/hf-model-cache @@ -44,7 +44,7 @@ jobs: run: node tools/stage-gateway-sidecar.mjs stage --target x86_64-pc-windows-msvc --source target/debug/promptforge-gateway.exe - name: Compile unsigned debug NSIS installer - uses: tauri-apps/tauri-action@v0 + uses: tauri-apps/tauri-action@84b9d35b5fc46c1e45415bdb6144030364f7ebc5 # v0 with: projectPath: crates/workshop args: --debug --bundles nsis --config tauri.nightly.conf.json From 0eda0f17ba46a9d32f3d0dc517d0020c4311f90e Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 14 Sep 2026 09:49:46 -0700 Subject: [PATCH 4/4] Pin third-party actions in the release workflows `release-workshop.yml` and `nightly.yml` referenced `dtolnay/rust-toolchain`, `Swatinem/rust-cache`, `tauri-apps/tauri-action`, and `softprops/action-gh-release` through mutable tags. Each reference is now pinned to its current commit SHA with the original ref kept in a trailing comment. The `release-workshop.yml` pins sit on the path that consumes `TAURI_SIGNING_PRIVATE_KEY`. --- .github/workflows/nightly.yml | 14 +++++++------- .github/workflows/release-workshop.yml | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 0b48d917c..f0630308c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -59,8 +59,8 @@ jobs: ARTIFACT: gateway-nightly-${{ needs.check.outputs.date }}-${{ needs.check.outputs.short-sha }}-linux-x64 steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Cache the embedding model uses: ./.github/actions/hf-model-cache - uses: actions/setup-node@v4 @@ -92,8 +92,8 @@ jobs: ARTIFACT: gateway-nightly-${{ needs.check.outputs.date }}-${{ needs.check.outputs.short-sha }}-linux-arm64 steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Cache the embedding model uses: ./.github/actions/hf-model-cache - uses: actions/setup-node@v4 @@ -149,8 +149,8 @@ jobs: runs-on: ${{ matrix.runs-on }} steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Cache the embedding model uses: ./.github/actions/hf-model-cache @@ -209,7 +209,7 @@ jobs: cp "$src" "crates/workshop/binaries/promptforge-gateway-$triple$ext" - name: Build the app - uses: tauri-apps/tauri-action@v0 + uses: tauri-apps/tauri-action@84b9d35b5fc46c1e45415bdb6144030364f7ebc5 # v0 with: projectPath: crates/workshop args: ${{ matrix.args }} --config tauri.nightly.conf.json diff --git a/.github/workflows/release-workshop.yml b/.github/workflows/release-workshop.yml index c392a6849..8b1b1a09d 100644 --- a/.github/workflows/release-workshop.yml +++ b/.github/workflows/release-workshop.yml @@ -99,10 +99,10 @@ jobs: [ "$actual" = "$expected" ] || { echo "checkout $actual does not match execution SHA $expected"; exit 1; } echo "Execution SHA: $actual" - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable - name: Cache cargo - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Add the Rust target if: matrix.name == 'macos-arm' || matrix.name == 'macos-intel' @@ -177,7 +177,7 @@ jobs: - name: Build the signed release app if: github.event_name == 'push' id: release-build - uses: tauri-apps/tauri-action@v0 + uses: tauri-apps/tauri-action@84b9d35b5fc46c1e45415bdb6144030364f7ebc5 # v0 env: TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} @@ -188,7 +188,7 @@ jobs: - name: Build the unsigned temporary app if: github.event_name == 'workflow_dispatch' id: temporary-build - uses: tauri-apps/tauri-action@v0 + uses: tauri-apps/tauri-action@84b9d35b5fc46c1e45415bdb6144030364f7ebc5 # v0 with: projectPath: crates/workshop args: ${{ matrix.args }} --config tauri.nightly.conf.json @@ -560,7 +560,7 @@ jobs: python3 -m json.tool dist/latest.json - name: Publish the release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: tag_name: ${{ github.ref_name }} name: PromptForge Workshop ${{ github.ref_name }}