Skip to content

actions/unpinned-tag does not flag unpinned reusable workflow calls (job-level uses:) #21659

@PhilipAtCisco

Description

@PhilipAtCisco

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@v3

reusable.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
  1. Create the CodeQL database:
  codeql database create .codeql-db --language=actions --source-root=.
  1. 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
  1. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions