Composite action should pin the actions it uses by commit SHA
ID |
action_unpinned_uses |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Family |
CI/ CD Security |
Tags |
ASVS50:v13.1.1, ASVS50:v15.2.1, attack-T1195.001, cicd-security, cwe-829, security, spvs10-v2.8.2, supply-chain |
Description
A composite GitHub Custom Action (runs.using: composite) whose own steps uses: a cross-repository action through a floating reference — a tag, a branch, or no reference at all — instead of pinning it to a 40-character commit SHA.
Local (./path) and Docker (docker://) references are not flagged: the former is first-party code that is scanned anyway, the latter is covered by action_mutable_docker_image.
This complements pipeline_external_dependencies, which checks the consuming workflow. This detector checks the action definition itself, where an unpinned dependency is easy to miss — a workflow author who diligently pins every uses: still inherits whatever the actions they call have left floating.
Security
A tag or branch is a mutable pointer: the upstream owner (or anyone who compromises that account) can repoint it at different code, and every consumer picks up the change on the next run without a diff to review. This is the tj-actions/changed-files class of supply-chain attack, where a widely used action’s tags were rewritten to exfiltrate CI secrets.
The blast radius is larger for an action than for a workflow. A composite action runs inside the caller’s job, with the caller’s secrets, token and workspace — so a substituted dependency reaches every repository that consumes the action, transitively, through a reference the consumer never wrote.
Floating references also break reproducibility: two runs of the same pipeline can execute different code with no change on the consumer’s side.
Mitigation / Fix
Pin every cross-repository uses: to a full 40-character commit SHA, keeping the human-readable version in a trailing comment:
runs:
using: composite
steps:
# Pinned — immutable:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# Avoid — mutable tag, can be repointed at malicious code:
# - uses: actions/checkout@v6
# Avoid — branch reference, changes on every upstream push:
# - uses: actions/checkout@main
A short SHA is not sufficient: pin the full 40 characters, since abbreviated hashes can collide and are accepted loosely by some tooling.
Update pins deliberately — review the upstream diff when moving to a new SHA — and let Dependabot (package-ecosystem: github-actions) raise the bumps so pinning does not mean going stale.