Harden workflows: gate self-hosted runner, scope tokens, pin actions - #40
Merged
Merged
Conversation
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.
`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.
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.
`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`.
vinniefalco
force-pushed
the
ci/workflow-hardening
branch
from
September 14, 2026 19:53
59d74e4 to
0eda0f1
Compare
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Security hardening for the Actions setup, from an audit of the workflow files against the standard PR-trigger threat model (fork PRs, secret exposure, self-hosted runners, supply chain).
1. Self-hosted runner reachable from pull requests
stt-miri.ymltriggers onpull_request, and itsnative-whisperjob runs on[self-hosted, windows, cuda]. On a public repo, any PR approved to run would compile and execute arbitrary test code on a persistent machine: persistence, runner-credential theft, GPU abuse, and network lateral movement are all in play, and no repository secret needs to be involved for that to hurt. The job now runs only on trusted code:Fork PRs keep the two Miri jobs, which run on GitHub-hosted runners. The other self-hosted workflows (
whisper-lib.yml,llama-cuda-blackwell.yml) trigger only on push-to-master and dispatch, so they needed no gate.2. Release workflow token scoped to read by default
promptforge-gateway-v-release.ymltriggers onpull_requestwithcontents: writeat the top level. Fork PRs are auto-downgraded to read-only, so this was not externally exploitable, but same-repo PRs randist planwith a write token. The default is nowcontents: read;plan,host, andannouncegrantcontents: writethemselves (plankeeps write becausedist host --steps=createmay create the release on tag pushes). Both edits carry HAND-EDIT comments so they get re-applied aftercargo dist generate.3. Script-injection hardening
The
planstep interpolatedgithub.ref_namedirectly into itsrun:command. Tag names can contain shell metacharacters, so the dist subcommand now travels throughDIST_PLAN_COMMAND. Thegithub.ref_nameuses inrelease-workshop.ymlwere audited and are safe as-is: they flow throughenv:or action inputs, never through shell interpolation.4. Third-party actions pinned to commit SHAs
Every non-GitHub action is now pinned to a full commit SHA, with the original ref in a trailing comment:
dtolnay/rust-toolchainstable(branch)6bed0761d98439e5a578e2877258200ad565ba87dtolnay/rust-toolchainnightly(branch)be39649afda95dbf70f87cce95f68b8d5797b296Swatinem/rust-cachev2(signed tag, 2.9.2)6323deb102c322ba6fcbdcafc7e3dddab59af2b6taiki-e/install-actionv23f74d7c16a4242f1c95561e98edc25d36adb4375taiki-e/install-actionnextest4dc1969decfa71b34f25aa7f3dd4656654d9ad1etauri-apps/tauri-actionv0(unsigned tag)84b9d35b5fc46c1e45415bdb6144030364f7ebc5softprops/action-gh-releasev23bb12739c298aeb8a4eeaf626c5b8d85266b0e65The
tauri-actionpin matters most: it consumesTAURI_SIGNING_PRIVATE_KEYinrelease-workshop.yml. GitHub-ownedactions/*references stay on major-version tags. Thedtolnay/rust-toolchainpins track moving branches and need periodic re-pinning; consider Dependabot for github-actions to automate that.What this PR does not change
native-whisperself-hosted job; all GitHub-hosted PR jobs are untouched.Follow-ups outside this PR (repo/org settings, need admin)
windows/cudaself-hosted runner is scoped to this repository only, not the whole cppalliance org.masterrequires theci-greencheck.Test plan
ci.ymlruns the full matrix;stt-miri.ymlruns Miri jobs;native-whisperruns because this PR is same-repo).native-whisperskipped and everything else green.gateway-v*tag push completesplan/host/announcewith the job-scoped write token.