-
Notifications
You must be signed in to change notification settings - Fork 1.9k
actions/unpinned-tag does not flag unpinned reusable workflow calls (job-level uses:) #21659
Description
Description
The actions/unpinned-tag query flags step-level uses: references that are not pinned to a
commit SHA, but silently ignores job-level uses: references (reusable workflow calls). Both
patterns carry the same supply chain risk when referencing a mutable tag or branch.
CodeQL version
2.25.1
Pack and query
- Pack:
codeql/actions-queries@0.6.24 - Query:
Security/CWE-829/UnpinnedActionsTag.ql - Suite:
actions/security-extended
Steps to reproduce
1. Create two workflow files.
unpinned.yaml — step-level action with an unpinned tag:
name: Test Step-Level Unpinned Action
on:
push:
permissions: {}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: hashicorp/setup-terraform@v3reusable.yaml — job-level reusable workflow call with an unpinned branch ref:
name: Test Reusable Workflow Unpinned
on:
push:
permissions: {}
jobs:
call-workflow:
uses: some-org/some-repo/.github/workflows/some-workflow.yml@main
secrets: inherit- Create the CodeQL database:
codeql database create .codeql-db --language=actions --source-root=.- Run the actions/security-extended suite:
codeql database analyze .codeql-db \
codeql/actions-queries:codeql-suites/actions-security-extended.qls \
--format=sarifv2.1.0 \
--output=results.sarif- Observe results:
actions/unpinned-tag @ unpinned.yaml:9 ← flagged ✓
The job-level uses: some-org/some-repo/...@main in reusable.yaml produces no finding.
Expected behavior
Both of the following patterns should produce an actions/unpinned-tag finding when
referencing a mutable ref:
# Step-level — currently flagged ✓
steps:
- uses: some-org/some-action@v1
# Job-level — currently NOT flagged ✗
jobs:
my-job:
uses: some-org/some-repo/.github/workflows/workflow.yml@main
Actual behavior
Only the step-level uses: is flagged. The job-level reusable workflow call produces no
finding regardless of the ref used.
Root cause analysis
In UnpinnedActionsTag.ql:34, the query selects only UsesStep:
from UsesStep uses, string nwo, string version, Workflow workflow, string name
UsesStep maps to UsesStepImpl extends StepImpl in the CodeQL actions AST — matching only
uses: keys inside a job's steps: list.
Job-level reusable workflow calls are a separate AST node, ExternalJobImpl extends JobImpl,
which this query never queries. Both node types expose getCallee() and getVersion() with
the same semantics, but only UsesStep is checked.
Suggested fix
Extend the query (or add a companion query) to also select from ExternalJob and apply the
same pinning checks already used for UsesStep:
- isPinnedCommit(version)
- isTrustedOwner(nwo)
- isImmutableAction(uses, nwo)