Skip to content

Commit 75cbb24

Browse files
committed
Actions: consume extracted lockfile pins
1 parent c6ea2b1 commit 75cbb24

17 files changed

Lines changed: 93 additions & 89 deletions

actions/ql/lib/change-notes/2026-07-09-pinned-by-lockfile.md

Lines changed: 0 additions & 4 deletions
This file was deleted.

actions/ql/lib/change-notes/2026-09-01-actions-lock-yaml.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
category: feature
33
---
44
* GitHub Actions databases now extract `actions.lock` files. The new `ActionsLock` class
5-
provides access to their YAML abstract syntax trees.
5+
provides access to their YAML abstract syntax trees and valid v0.0.2 workflow pins.

actions/ql/lib/codeql/actions/Lock.qll

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,38 @@ private import codeql.actions.ast.internal.Yaml
77
/** An `actions.lock` file. */
88
class ActionsLock extends YamlDocument {
99
ActionsLock() { this.getFile().getBaseName() = "actions.lock" }
10+
11+
/**
12+
* Holds if the v0.0.2 lockfile records `nwo` at `ref` for `workflowPath` with a full commit
13+
* digest. Repository pins also cover sub-actions such as `actions/cache/save`.
14+
*/
15+
bindingset[nwo]
16+
predicate pins(string workflowPath, string nwo, string ref) {
17+
this.getFile().getRelativePath() = ".github/workflows/actions.lock" and
18+
exists(
19+
YamlMapping root, YamlSequence workflowPins, YamlScalar pinNode, YamlMapping dependency,
20+
string pin, string pinnedNwo
21+
|
22+
root = this and
23+
root.lookup("version").(YamlScalar).getValue() = "v0.0.2" and
24+
root.lookup("workflows").(YamlMapping).lookup(workflowPath) = workflowPins and
25+
workflowPins.getElement(_) = pinNode and
26+
pin = pinNode.getValue() and
27+
pinnedNwo = pin.regexpCapture("^([^/@:]+/[^/@:]+)@([^:]+)$", 1) and
28+
ref = pin.regexpCapture("^([^/@:]+/[^/@:]+)@([^:]+)$", 2) and
29+
pinnedNwo = pinnedNwo.toLowerCase() and
30+
(
31+
nwo.toLowerCase() = pinnedNwo
32+
or
33+
nwo.toLowerCase().prefix(pinnedNwo.length() + 1) = pinnedNwo + "/"
34+
) and
35+
root.lookup("dependencies").(YamlMapping).lookup(pin) = dependency and
36+
dependency.lookup("ref").(YamlScalar).getValue() = ref and
37+
dependency
38+
.lookup("commit")
39+
.(YamlScalar)
40+
.getValue()
41+
.regexpMatch("^(sha1-[A-Fa-f0-9]{40}|sha256-[A-Fa-f0-9]{64})$")
42+
)
43+
}
1044
}

actions/ql/lib/codeql/actions/config/Config.qll

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -135,26 +135,6 @@ predicate trustedActionsOwnerDataModel(string owner) {
135135
Extensions::trustedActionsOwnerDataModel(owner)
136136
}
137137

138-
/**
139-
* MaD models for `uses` references pinned by the repository's Actions lockfile
140-
* (`.github/workflows/actions.lock`).
141-
* Fields:
142-
* - workflow_path: repo-relative path of the file containing the `uses:` reference
143-
* - nwo: referenced action, optionally including a sub-action path (e.g. `actions/cache/save`)
144-
* - ref: the ref as written in `uses:` (e.g. `v4`)
145-
*/
146-
bindingset[nwo]
147-
predicate pinnedByLockfileDataModel(string workflow_path, string nwo, string ref) {
148-
exists(string pinnedNwo |
149-
Extensions::pinnedByLockfileDataModel(workflow_path, pinnedNwo, ref) and
150-
(
151-
nwo.toLowerCase() = pinnedNwo
152-
or
153-
nwo.toLowerCase().prefix(pinnedNwo.length() + 1) = pinnedNwo + "/"
154-
)
155-
)
156-
}
157-
158138
/**
159139
* MaD models for untrusted git commands
160140
* Fields:

actions/ql/lib/codeql/actions/config/ConfigExtensions.qll

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -68,23 +68,6 @@ extensible predicate immutableActionsDataModel(string action);
6868
*/
6969
extensible predicate trustedActionsOwnerDataModel(string owner);
7070

71-
/**
72-
* Holds if the `uses` reference `nwo`@`ref` in the workflow or composite action file at
73-
* `workflow_path` is pinned by an entry in the repository's Actions lockfile
74-
* (`.github/workflows/actions.lock`).
75-
*
76-
* Supply rows from a model pack generated with the canonical parser at
77-
* `github.com/github/actions-lockfile/go/pkg/lockfile`. Each lockfile entry binds an `nwo`@`ref`
78-
* to a verified commit SHA. Without such a model pack this predicate is empty.
79-
*
80-
* Fields:
81-
* - `workflow_path`: repo-relative path of the file containing the `uses:` reference,
82-
* e.g. `.github/workflows/ci.yml`.
83-
* - `nwo`: canonical owner and repository from the lockfile pin, e.g. `actions/cache`.
84-
* - `ref`: the ref (tag or branch) as written in `uses:`, e.g. `v4`.
85-
*/
86-
extensible predicate pinnedByLockfileDataModel(string workflow_path, string nwo, string ref);
87-
8871
/**
8972
* Holds for git commands that may introduce untrusted data when called on an attacker controlled branch.
9073
*/

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ where
6464
not isSelfRepository(nwo) and
6565
not exists(UsesStep step |
6666
uses = step and
67-
pinnedByLockfileDataModel(step.getLocation().getFile().getRelativePath(), nwo, version)
67+
exists(ActionsLock lock |
68+
lock.pins(step.getLocation().getFile().getRelativePath(), nwo, version)
69+
)
6870
) and
6971
not (
7072
if uses instanceof UsesStep and isContainerImage(nwo)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
---
22
category: minorAnalysis
33
---
4-
* The `actions/unpinned-tag` query no longer reports `uses:` references recorded by the new `pinnedByLockfileDataModel` extensible predicate. Lockfile pins match repository sub-actions while preserving ref casing.
4+
* The `actions/unpinned-tag` query no longer reports action references pinned by a valid v0.0.2 `.github/workflows/actions.lock` entry for the enclosing workflow.

actions/ql/test/qlpack.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,4 @@ extractor: actions
1111
tests: .
1212
warnOnImplicitThis: true
1313
dataExtensions:
14-
- query-tests/Security/CWE-829/*.model.yml
1514
- output-clobbering.model.yml
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
version: v0.0.2
2+
workflows:
3+
.github/workflows/rust-ci.yml:
4+
- dtolnay/rust-toolchain@v1
5+
- mismatched/action@v1
6+
- malformed/action@v1
7+
- missing/action@v1
8+
.github/workflows/other.yml:
9+
- other-workflow/action@v1
10+
dependencies:
11+
dtolnay/rust-toolchain@v1:
12+
ref: v1
13+
commit: sha1-6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772
14+
owner_id: 1940490
15+
repo_id: 260749683
16+
other-workflow/action@v1:
17+
ref: v1
18+
commit: sha1-1111111111111111111111111111111111111111
19+
owner_id: 1
20+
repo_id: 2
21+
mismatched/action@v1:
22+
ref: V1
23+
commit: sha1-2222222222222222222222222222222222222222
24+
owner_id: 3
25+
repo_id: 4
26+
malformed/action@v1:
27+
ref: v1
28+
commit: 6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772
29+
owner_id: 5
30+
repo_id: 6
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
on:
2+
pull_request
3+
4+
jobs:
5+
build:
6+
runs-on: ubuntu-latest
7+
steps:
8+
- uses: dtolnay/rust-toolchain@v1
9+
- uses: DToLnAy/RuSt-ToOlChAiN/save@v1
10+
- uses: dtolnay/rust-toolchain@V1
11+
- uses: other-workflow/action@v1
12+
- uses: mismatched/action@v1
13+
- uses: malformed/action@v1
14+
- uses: missing/action@v1
15+
reusable:
16+
uses: dtolnay/rust-toolchain/.github/workflows/reusable.yml@v1

0 commit comments

Comments
 (0)