Skip to content

Commit 14119c7

Browse files
author
Dave Bartolomeo
committed
Merge remote-tracking branch 'origin/master' into dbartol/move-to-codeql
2 parents 1f3bab2 + 064c983 commit 14119c7

17 files changed

+200
-11
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,17 @@ predicate vulnerableActionsDataModel(
119119
Extensions::vulnerableActionsDataModel(action, vulnerable_version, vulnerable_sha, fixed_version)
120120
}
121121

122+
/**
123+
* MaD models for immutable actions
124+
* Fields:
125+
* - action: action name
126+
*/
127+
predicate immutableActionsDataModel(
128+
string action
129+
) {
130+
Extensions::immutableActionsDataModel(action)
131+
}
132+
122133
/**
123134
* MaD models for untrusted git commands
124135
* Fields:

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ extensible predicate vulnerableActionsDataModel(
5858
string action, string vulnerable_version, string vulnerable_sha, string fixed_version
5959
);
6060

61+
/**
62+
* Holds for actions that are known to be immutable.
63+
*/
64+
extensible predicate immutableActionsDataModel(
65+
string action
66+
);
67+
6168
/**
6269
* Holds for git commands that may introduce untrusted data when called on an attacker controlled branch.
6370
*/
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import actions
2+
3+
class UnversionedImmutableAction extends UsesStep {
4+
string immutable_action;
5+
6+
UnversionedImmutableAction() {
7+
isImmutableAction(this, immutable_action) and
8+
not isSemVer(this.getVersion())
9+
}
10+
}
11+
12+
bindingset[version]
13+
predicate isSemVer(string version) {
14+
// https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string with optional v prefix
15+
version.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-]+)*))?$")
16+
17+
// or N or N.x or N.N.x with optional v prefix
18+
or version.regexpMatch("^v?[1-9]\\d*$")
19+
or version.regexpMatch("^v?[1-9]\\d*\\.(x|0|([1-9]\\d*))$")
20+
or version.regexpMatch("^v?[1-9]\\d*\\.(0|([1-9]\\d*))\\.(x|0|([1-9]\\d*))$")
21+
22+
// or latest which will work
23+
or version = "latest"
24+
}
25+
26+
predicate isImmutableAction(UsesStep actionStep, string actionName) {
27+
immutableActionsDataModel(actionName) and
28+
actionStep.getCallee() = actionName
29+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
extensions:
2+
- addsTo:
3+
pack: github/actions-all
4+
extensible: immutableActionsDataModel
5+
data:
6+
- ["actions/checkout"]
7+
- ["actions/cache"]
8+
- ["actions/setup-node"]
9+
- ["actions/upload-artifact"]
10+
- ["actions/setup-python"]
11+
- ["actions/download-artifact"]
12+
- ["actions/github-script"]
13+
- ["actions/setup-java"]
14+
- ["actions/setup-go"]
15+
- ["actions/upload-pages-artifact"]
16+
- ["actions/deploy-pages"]
17+
- ["actions/setup-dotnet"]
18+
- ["actions/stale"]
19+
- ["actions/labeler"]
20+
- ["actions/create-github-app-token"]
21+
- ["actions/configure-pages"]
22+
- ["octokit/request-action"]

ql/lib/qlpack.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
library: true
33
warnOnImplicitThis: true
44
name: codeql/actions-all
5-
version: 0.1.85
5+
version: 0.2.0
66
dependencies:
77
codeql/util: ^1.0.1
88
codeql/yaml: ^1.0.1

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Using a tag for a 3rd party Action that is not pinned to a commit can lead to ex
66

77
## Recommendations
88

9-
Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload. When selecting a SHA, you should verify it is from the action's repository and not a repository fork.
9+
Pinning an action to a full length commit SHA is currently the only way to use a non-immutable action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload. When selecting a SHA, you should verify it is from the action's repository and not a repository fork.
1010

1111
## Examples
1212

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
2-
* @name Unpinned tag for 3rd party Action in workflow
3-
* @description Using a tag for a 3rd party Action that is not pinned to a commit can lead to executing an untrusted Action through a supply chain attack.
2+
* @name Unpinned tag for a non-immutable Action in workflow
3+
* @description Using a tag for a non-immutable Action that is not pinned to a commit can lead to executing an untrusted Action through a supply chain attack.
44
* @kind problem
55
* @security-severity 5.0
66
* @problem.severity recommendation
@@ -12,13 +12,14 @@
1212
*/
1313

1414
import actions
15+
import codeql.actions.security.UseOfUnversionedImmutableAction
1516

1617
bindingset[version]
1718
private predicate isPinnedCommit(string version) { version.regexpMatch("^[A-Fa-f0-9]{40}$") }
1819

1920
bindingset[repo]
2021
private predicate isTrustedOrg(string repo) {
21-
exists(string org | org in ["actions", "github", "advanced-security"] | repo.matches(org + "/%"))
22+
repo.matches(["actions", "github", "advanced-security"] + "/%")
2223
}
2324

2425
from UsesStep uses, string repo, string version, Workflow workflow, string name
@@ -32,7 +33,8 @@ where
3233
) and
3334
uses.getVersion() = version and
3435
not isTrustedOrg(repo) and
35-
not isPinnedCommit(version)
36+
not isPinnedCommit(version) and
37+
not isImmutableAction(uses, repo)
3638
select uses.getCalleeNode(),
3739
"Unpinned 3rd party Action '" + name + "' step $@ uses '" + repo + "' with ref '" + version +
3840
"', not a pinned commit hash", uses, uses.toString()
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Unversioned Immutable Action
2+
3+
## Description
4+
5+
Using an immutable action without indicating proper semantic version will result in the version being resolved to a tag that is mutable. This means the action code can between runs and without the user's knowledge. Using an immutable action with proper semantic versioning will resolve to the exact version
6+
of the action stored in the GitHub package registry. The action code will not change between runs.
7+
8+
## Recommendations
9+
10+
When using [immutable actions](https://github.com/github/package-registry-team/blob/main/docs/immutable-actions/immutable-actions-howto.md) use the full semantic version of the action. This will ensure that the action is resolved to the exact version stored in the GitHub package registry. This will prevent the action code from changing between runs.
11+
12+
## Examples
13+
14+
### Incorrect Usage
15+
16+
```yaml
17+
- uses: actions/checkout@some-tag
18+
- uses: actions/[email protected]
19+
```
20+
21+
### Correct Usage
22+
23+
```yaml
24+
- uses: actions/[email protected]
25+
```
26+
27+
## References
28+
29+
- [Consuming immutable actions]()
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @name Unversioned Immutable Action
3+
* @description Using an Immutable Action without a semantic version tag opts out of the protections of Immutable Action
4+
* @kind problem
5+
* @problem.severity recommendation
6+
* @precision high
7+
* @id actions/unversioned-immutable-action
8+
* @tags security
9+
* actions
10+
* external/cwe/cwe-829
11+
*/
12+
13+
import actions
14+
import codeql.actions.security.UseOfUnversionedImmutableAction
15+
16+
from UnversionedImmutableAction step
17+
select step,
18+
"The workflow is using an eligible immutable action ($@) without semantic versioning", step,
19+
step.getCallee()

ql/src/qlpack.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
library: false
33
name: codeql/actions-queries
4-
version: 0.1.85
4+
version: 0.2.0
55
groups: [actions, queries]
66
suites: codeql-suites
77
extractor: actions

0 commit comments

Comments
 (0)