2.9 KiB
Supply Chain
A workflow runs other people's code every time it uses: an action. Those actions execute with
your token and (on privileged triggers) your secrets, so their integrity is your integrity.
Pin Third-Party Actions to a Commit SHA
Tags (@v4) and branches (@main) are mutable — the upstream owner (or anyone who compromises
them) can repoint them to new code without you changing a line. A full 40-character commit SHA is
immutable.
# Mutable — the tag can be moved to malicious code
- uses: some-org/some-action@v3
# Pinned — this exact tree, forever
- uses: some-org/some-action@3f1e0a9c8b7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f # v3.2.1
Rules:
- Third-party actions (anything not
actions/*orgithub/*) → MUST be SHA-pinned. Flag tags and branches as HIGH. @main/@master→ HIGH regardless of publisher; that is unversioned "latest".- First-party
actions/*→ SHA-pinning is the hardened recommendation (LOW if only tag-pinned). - Keep a trailing
# vX.Y.Zcomment so humans and Dependabot can read the intended version.
This is not theoretical: real incidents have seen popular actions' tags repointed to code that exfiltrated secrets from every workflow that referenced the mutable tag.
Let Dependabot Update the Pins
SHA pins go stale. Enable Dependabot for the github-actions ecosystem so updates arrive as
reviewable PRs (it understands the # vX.Y.Z comment and bumps the SHA):
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
Artifact and Cache Poisoning
- An artifact uploaded by an untrusted
pull_requestbuild is untrusted data. A privilegedworkflow_runmay download it, but must treat it as data only — never execute it, and validate paths when extracting (a crafted artifact can contain../path-traversal entries). - Caches are keyed and can be populated by less-privileged runs; do not trust cached build outputs to be untampered in a privileged context.
Self-Hosted Runners on Public Repos
Default (GitHub-hosted) runners are ephemeral — a fresh VM per job, destroyed after. Self-hosted runners persist, so untrusted fork PR code running on one can:
- Leave behind tools/backdoors for the next job,
- Read other repositories' checkouts or credentials on the same machine,
- Pivot into your network.
Never use self-hosted runners for workflows that public forks can trigger. If you must, use ephemeral, isolated, single-use runners and never expose secrets to fork-triggered jobs.
checkout Credential Persistence
actions/checkout writes the token into .git/config by default so later git steps can push.
If the job subsequently runs untrusted code, that code can read the token. Set
persist-credentials: false when you don't need to push, especially before running build/test of
untrusted code.