Skip to content

Commit 2b1cbcc

Browse files
Actions: report floating tags of immutable Actions in actions/unpinned-tag
The query exempted any Action on the immutable-actions list regardless of the ref used. Only complete version tags (vX.Y.Z) and commit SHAs are immutable; floating tags such as v4, v4.1 or main are moved by the maintainers. Only exempt an immutable Action when it is pinned to a complete SemVer version. Fixes #22414 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent e7bc1c6 commit 2b1cbcc

6 files changed

Lines changed: 53 additions & 3 deletions

File tree

‎actions/ql/lib/codeql/actions/security/UseOfUnversionedImmutableAction.qll‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,23 @@ class UnversionedImmutableAction extends UsesStep {
77
}
88
}
99

10+
/**
11+
* Holds if `version` is a complete SemVer version (`X.Y.Z`, optionally with a `v` prefix,
12+
* pre-release and build metadata), as opposed to a floating tag such as `v4` or `v4.1`.
13+
*
14+
* Only complete version tags (and full commit SHAs) of an immutable Action are immutable.
15+
* Floating tags are moved by the Action's maintainers and so can change under a consumer.
16+
*/
1017
bindingset[version]
11-
predicate isSemVer(string version) {
18+
predicate isFullSemVer(string version) {
1219
// https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string with optional v prefix
1320
version
14-
.regexpMatch("^v?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$") or
21+
.regexpMatch("^v?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$")
22+
}
23+
24+
bindingset[version]
25+
predicate isSemVer(string version) {
26+
isFullSemVer(version) or
1527
// or N or N.x or N.N.x with optional v prefix
1628
version.regexpMatch("^v?[1-9]\\d*$") or
1729
version.regexpMatch("^v?[1-9]\\d*\\.(x|0|([1-9]\\d*))$") or

‎actions/ql/src/Security/CWE-829/UnpinnedActionsTag.ql‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ where
6868
then isPinnedContainer(version)
6969
else isPinnedCommit(version)
7070
) and
71-
not exists(UsesStep step | uses = step and isImmutableAction(step, nwo)) and
71+
// An immutable Action is only immutable when referenced by a complete version tag (or SHA).
72+
// Floating tags such as `v4` or `v4.1` are moved by the maintainers and remain mutable.
73+
not exists(UsesStep step | uses = step and isImmutableAction(step, nwo) and isFullSemVer(version)) and
7274
if uses instanceof ExternalJob
7375
then
7476
message =
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The `actions/unpinned-tag` query no longer treats an immutable Action as pinned when it is referenced by a floating tag such as `v4`, `v4.1`, `main` or `latest`. Only complete version tags (for example `v4.2.2`) and full commit SHAs are immutable, so floating tags are now reported for immutable Actions that are not covered by a trusted owner.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Immutable Actions
2+
on: push
3+
jobs:
4+
build:
5+
runs-on: ubuntu-latest
6+
steps:
7+
# `actions` is distrusted via `!actions`. `actions/checkout` is an immutable Action, but only
8+
# a complete version tag (or a commit SHA) is immutable, so floating tags are still reported.
9+
- uses: actions/checkout@v4 # $ Alert
10+
- uses: actions/checkout@v4.1 # $ Alert
11+
- uses: actions/checkout@main # $ Alert
12+
- uses: actions/checkout@latest # $ Alert
13+
# Complete version tags of an immutable Action are not reported.
14+
- uses: actions/checkout@v4.2.2
15+
- uses: actions/checkout@4.2.2
16+
- uses: actions/checkout@v4.2.2-rc.1
17+
# A pinned commit is not reported.
18+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
19+
# `octokit` is not trusted by default; `octokit/request-action` is an immutable Action.
20+
- uses: octokit/request-action@v2 # $ Alert
21+
- uses: octokit/request-action@v2.x # $ Alert
22+
- uses: octokit/request-action@v2.1.9
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
| .github/workflows/unpinned_first_party.yml:9:15:9:42 | actions/first-interaction@v1 | Unpinned 3rd party Action 'Unpinned first-party actions' step $@ uses 'actions/first-interaction' with ref 'v1', not a pinned commit hash | .github/workflows/unpinned_first_party.yml:9:9:11:6 | Uses Step | Uses Step |
22
| .github/workflows/unpinned_first_party.yml:13:15:13:24 | foo/bar@v1 | Unpinned 3rd party Action 'Unpinned first-party actions' step $@ uses 'foo/bar' with ref 'v1', not a pinned commit hash | .github/workflows/unpinned_first_party.yml:13:9:13:35 | Uses Step | Uses Step |
3+
| .github/workflows/unpinned_immutable.yml:9:15:9:33 | actions/checkout@v4 | Unpinned 3rd party Action 'Immutable Actions' step $@ uses 'actions/checkout' with ref 'v4', not a pinned commit hash | .github/workflows/unpinned_immutable.yml:9:9:10:6 | Uses Step | Uses Step |
4+
| .github/workflows/unpinned_immutable.yml:10:15:10:35 | actions/checkout@v4.1 | Unpinned 3rd party Action 'Immutable Actions' step $@ uses 'actions/checkout' with ref 'v4.1', not a pinned commit hash | .github/workflows/unpinned_immutable.yml:10:9:11:6 | Uses Step | Uses Step |
5+
| .github/workflows/unpinned_immutable.yml:11:15:11:35 | actions/checkout@main | Unpinned 3rd party Action 'Immutable Actions' step $@ uses 'actions/checkout' with ref 'main', not a pinned commit hash | .github/workflows/unpinned_immutable.yml:11:9:12:6 | Uses Step | Uses Step |
6+
| .github/workflows/unpinned_immutable.yml:12:15:12:37 | actions/checkout@latest | Unpinned 3rd party Action 'Immutable Actions' step $@ uses 'actions/checkout' with ref 'latest', not a pinned commit hash | .github/workflows/unpinned_immutable.yml:12:9:14:6 | Uses Step | Uses Step |
7+
| .github/workflows/unpinned_immutable.yml:20:15:20:39 | octokit/request-action@v2 | Unpinned 3rd party Action 'Immutable Actions' step $@ uses 'octokit/request-action' with ref 'v2', not a pinned commit hash | .github/workflows/unpinned_immutable.yml:20:9:21:6 | Uses Step | Uses Step |
8+
| .github/workflows/unpinned_immutable.yml:21:15:21:41 | octokit/request-action@v2.x | Unpinned 3rd party Action 'Immutable Actions' step $@ uses 'octokit/request-action' with ref 'v2.x', not a pinned commit hash | .github/workflows/unpinned_immutable.yml:21:9:22:6 | Uses Step | Uses Step |

‎actions/ql/test/query-tests/Security/CWE-829/UnpinnedActionsTag.expected‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
| .github/workflows/issue_comment_3rd_party_action.yml:14:15:14:52 | xt0rted/pull-request-comment-branch@v2 | Unpinned 3rd party Action 'PR head from 3rd party action' step $@ uses 'xt0rted/pull-request-comment-branch' with ref 'v2', not a pinned commit hash | .github/workflows/issue_comment_3rd_party_action.yml:12:9:16:6 | Uses Step: comment-branch | Uses Step: comment-branch |
1616
| .github/workflows/issue_comment_3rd_party_action.yml:27:15:27:52 | xt0rted/pull-request-comment-branch@v2 | Unpinned 3rd party Action 'PR head from 3rd party action' step $@ uses 'xt0rted/pull-request-comment-branch' with ref 'v2', not a pinned commit hash | .github/workflows/issue_comment_3rd_party_action.yml:25:9:30:6 | Uses Step: comment-branch | Uses Step: comment-branch |
1717
| .github/workflows/issue_comment_3rd_party_action.yml:41:15:41:42 | eficode/resolve-pr-refs@main | Unpinned 3rd party Action 'PR head from 3rd party action' step $@ uses 'eficode/resolve-pr-refs' with ref 'main', not a pinned commit hash | .github/workflows/issue_comment_3rd_party_action.yml:39:9:45:6 | Uses Step: refs | Uses Step: refs |
18+
| .github/workflows/issue_comment_octokit2.yml:13:15:13:41 | octokit/request-action@v2.x | Unpinned 3rd party Action 'Octokit (heuristics)' step $@ uses 'octokit/request-action' with ref 'v2.x', not a pinned commit hash | .github/workflows/issue_comment_octokit2.yml:12:9:19:6 | Uses Step: fetch_issue | Uses Step: fetch_issue |
19+
| .github/workflows/issue_comment_octokit2.yml:20:15:20:43 | octokit/request-action@v2.x.x | Unpinned 3rd party Action 'Octokit (heuristics)' step $@ uses 'octokit/request-action' with ref 'v2.x.x', not a pinned commit hash | .github/workflows/issue_comment_octokit2.yml:19:9:26:6 | Uses Step: fetch_pr | Uses Step: fetch_pr |
1820
| .github/workflows/issue_comment_octokit2.yml:34:15:34:42 | some-action/some-repo@latest | Unpinned 3rd party Action 'Octokit (heuristics)' step $@ uses 'some-action/some-repo' with ref 'latest', not a pinned commit hash | .github/workflows/issue_comment_octokit2.yml:33:9:37:6 | Uses Step | Uses Step |
21+
| .github/workflows/issue_comment_octokit.yml:13:15:13:41 | octokit/request-action@v2.x | Unpinned 3rd party Action 'Octokit (heuristics)' step $@ uses 'octokit/request-action' with ref 'v2.x', not a pinned commit hash | .github/workflows/issue_comment_octokit.yml:12:9:19:6 | Uses Step: fetch_issue | Uses Step: fetch_issue |
22+
| .github/workflows/issue_comment_octokit.yml:20:15:20:41 | octokit/request-action@v2.x | Unpinned 3rd party Action 'Octokit (heuristics)' step $@ uses 'octokit/request-action' with ref 'v2.x', not a pinned commit hash | .github/workflows/issue_comment_octokit.yml:19:9:26:6 | Uses Step: fetch_pr | Uses Step: fetch_pr |
1923
| .github/workflows/label_trusted_checkout1.yml:20:13:20:36 | completely/fakeaction@v2 | Unpinned 3rd party Action 'label_trusted_checkout1.yml' step $@ uses 'completely/fakeaction' with ref 'v2', not a pinned commit hash | .github/workflows/label_trusted_checkout1.yml:20:7:24:4 | Uses Step | Uses Step |
2024
| .github/workflows/label_trusted_checkout1.yml:24:13:24:37 | fakerepo/comment-on-pr@v1 | Unpinned 3rd party Action 'label_trusted_checkout1.yml' step $@ uses 'fakerepo/comment-on-pr' with ref 'v1', not a pinned commit hash | .github/workflows/label_trusted_checkout1.yml:24:7:27:21 | Uses Step | Uses Step |
2125
| .github/workflows/label_trusted_checkout2.yml:21:13:21:36 | completely/fakeaction@v2 | Unpinned 3rd party Action 'label_trusted_checkout2.yml' step $@ uses 'completely/fakeaction' with ref 'v2', not a pinned commit hash | .github/workflows/label_trusted_checkout2.yml:21:7:25:4 | Uses Step | Uses Step |

0 commit comments

Comments
 (0)