Harden GitHub workflows for trustless, fork-based collaboration#65
Merged
conradbzura merged 4 commits intomainfrom Mar 23, 2026
Merged
Harden GitHub workflows for trustless, fork-based collaboration#65conradbzura merged 4 commits intomainfrom
conradbzura merged 4 commits intomainfrom
Conversation
The label-pr workflow and its add-label composite action are no longer used and add unnecessary attack surface via the MY_TOKEN secret and overly broad id-token: write permission.
Pin all third-party actions to immutable commit SHAs to prevent
supply-chain attacks via tag mutation.
Move user-controllable GitHub expressions (github.base_ref,
github.head_ref, github.event.inputs.*, inputs.version,
matrix.secret_name, step outputs, github.workflow) from run: blocks
into env: blocks to eliminate expression injection vectors.
Fix command injection in get-touched-files action by replacing the
unsafe eval of the pathspec input with IFS-based word splitting.
Add persist-credentials: false to all non-pushing checkout steps to
prevent token leakage.
Scope job permissions to least privilege by adding top-level
permissions: {} and explicit per-job permissions where needed.
Pass the PyPI token via PYPI_TOKEN environment variable instead of a
CLI argument to avoid exposing it in process listings. Update
publish-distribution.sh to check the PYPI_TOKEN env var before
falling back to the keychain.
Add concurrency blocks to all workflows: cancel-in-progress for test
runs, no-cancel for release and sync workflows.
Fix drop-release job to depend on all downstream jobs with an
always() guard so the release branch is only deleted after all
publish steps complete.
Add idempotency guard to sync-branches to skip PR creation when a
sync PR already exists.
2136241 to
8618a52
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
Harden all GitHub Actions workflows and composite actions for secure, fork-friendly collaboration. Pin every third-party action to its commit SHA, move all user-controllable
${{ }}expressions out ofrun:blocks intoenv:blocks to prevent expression injection, addpersist-credentials: falseto every checkout that does not need push access, scope token permissions per-job, pass the PyPI token via environment variable instead of CLI argument, add concurrency controls, and remove the unnecessarylabel-prworkflow entirely. — Closes #64Proposed changes
1. Remove
label-pr.yamlandadd-labelcomposite actionDelete
.github/workflows/label-pr.yamland.github/actions/add-label/action.yaml. The workflow passessecrets.MY_TOKENon every PR event to add a convenience label — the attack surface is not worth the automation.2. Pin all third-party actions to commit SHAs
Replace every mutable version-tag reference with its current commit SHA and a trailing version comment:
actions/checkoutv434e114876b0b11c390a56381ad16ebd13914f8d5prefix-dev/setup-pixiv0.8.1ba3bb36eb2066252b2363392b7739741bb777659astral-sh/setup-uvv5e58605a9b6da7c637471fab8847a5e5a6b8df081actions/upload-artifactv4ea165f8d65b6e75b540449e92b4886f43607fa02actions/download-artifactv4d3f86a106a0bac45b974a628896c90dbdf5c8093sigstore/gh-action-sigstore-pythonv3.0.0f514d46b907ebcd5bedc05145c03b69c1edd8b463. Prevent expression injection
Move every user-controllable GitHub context expression (
github.base_ref,github.head_ref,github.workflow,github.event.inputs.*,inputs.version,matrix.secret_name) from inlinerun:interpolation intoenv:blocks, then reference them as quoted shell variables. Affected files:publish-release.yaml—github.base_ref,github.head_refin thebump-versionjob'scasestatement;needs.bump-version.outputs.versionintag-versioncut-release.yaml—github.event.inputs.release-typein shell;github.workflowin PR body construction;steps.cut-release-branch.outputs.versionin tag pushsync-branches.yaml—github.workflow,github.base_refin PR body and title constructionvalidate-repo.yaml—matrix.secret_namein error messagepublish-github-release/action.yaml—inputs.versioningh release createandgh release uploadbuild-release/action.yaml—inputs.versioninuv build --out-dir4. Add
persist-credentials: falseto all non-pushing checkoutsEvery
actions/checkoutstep that does not subsequentlygit pushsetspersist-credentials: falseto prevent the token from leaking to later steps. This applies to:run-tests.yamlpublish-release.yaml—build-release,publish-github-release,publish-pypi-releasejobscut-release.yaml—verify-code-changes,build-release,publish-github-release,publish-pypi-releasejobsvalidate-repo.yaml5. Pass PyPI token via environment variable
In
publish-pypi-release/action.yaml, pass the token through an environment variable (PYPI_TOKEN) instead of a positional CLI argument (visible in/proc/*/cmdline). Updatepublish-distribution.shto read$PYPI_TOKENfrom the environment when no positional token is supplied.6. Fix command injection in
get-touched-filesReplace the unsafe
eval "pathspec=('${{ inputs.pathspec }}')"with a safeIFS=' ' read -rapattern using an env var.7. Scope permissions and add concurrency controls
permissions: {}to all workflows, with explicit per-job grants for only the permissions each job actually needs.concurrency:blocks:cancel-in-progress: truefor test workflows,cancel-in-progress: falsefor release/sync workflows.8. Fix
drop-releasedependency completenessMake
drop-releasedepend on all publish jobs (publish-github-release,publish-pypi-release) in addition tobuild-release, withif: always() && !contains(needs.*.result, 'failure')to prevent deleting the release branch if publishing fails.9. Add idempotency guard to
sync-branchesBefore creating a sync PR, check whether one already exists with
gh pr list --base ... --head ... --state open. This prevents failures when the workflow re-runs on an already-synced merge.Implementation plan
.github/workflows/label-pr.yamland.github/actions/add-label/action.yaml${{ }}expressions fromrun:blocks intoenv:blocks across all workflows and composite actionspersist-credentials: falseto every non-pushingactions/checkoutsteppublish-pypi-release/action.yamlto pass PyPI token via env var; updatepublish-distribution.shto read$PYPI_TOKENfrom the environmentget-touched-files/action.yamlby replacingevalwith safe input parsingpermissionsper-job and remove unusedid-token: writegrantsconcurrencyblocks to all workflowsdrop-releaseinpublish-release.yamlto depend on all publish jobssync-branches.yamlPR creation steps