Skip to content

Commit e7bb47f

Browse files
committed
ruby: add MaD model for permissions needed by actions
Use this to suggest minimal set of nedded permissions
1 parent 279e9e2 commit e7bb47f

File tree

8 files changed

+99
-6
lines changed

8 files changed

+99
-6
lines changed

actions/ql/lib/codeql/actions/Ast.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,8 @@ class Step extends AstNode instanceof StepImpl {
242242

243243
If getIf() { result = super.getIf() }
244244

245+
AstNode getUses() { result = super.getUses() }
246+
245247
StepsContainer getContainer() { result = super.getContainer() }
246248

247249
Step getNextStep() { result = super.getNextStep() }

actions/ql/lib/codeql/actions/ast/internal/Ast.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,8 @@ class StepImpl extends AstNodeImpl, TStepNode {
11941194
/** Gets the value of the `if` field in this step, if any. */
11951195
IfImpl getIf() { result.getNode() = n.lookup("if") }
11961196

1197+
AstNodeImpl getUses() { result.getNode() = n.lookup("uses") }
1198+
11971199
/** Gets the Runs or LocalJob that this step is in. */
11981200
StepsContainerImpl getContainer() {
11991201
result = this.getParentNode().(RunsImpl) or

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,13 @@ predicate untrustedGitCommandDataModel(string cmd_regex, string flag) {
154154
predicate untrustedGhCommandDataModel(string cmd_regex, string flag) {
155155
Extensions::untrustedGhCommandDataModel(cmd_regex, flag)
156156
}
157+
158+
/**
159+
* MaD models for permissions needed by actions
160+
* Fields:
161+
* - action: action name
162+
* - permission: permission name
163+
*/
164+
predicate actionsPermissionsDataModel(string action, string permission) {
165+
Extensions::actionsPermissionsDataModel(action, permission)
166+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,8 @@ extensible predicate untrustedGitCommandDataModel(string cmd_regex, string flag)
7777
* Holds for gh commands that may introduce untrusted data
7878
*/
7979
extensible predicate untrustedGhCommandDataModel(string cmd_regex, string flag);
80+
81+
/**
82+
* Holds if `action` needs `permission` to run.
83+
*/
84+
extensible predicate actionsPermissionsDataModel(string action, string permission);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/actions-all
4+
extensible: actionsPermissionsDataModel
5+
data:
6+
- ["actions/checkout", "contents: read"]
7+
- ["actions/setup-node", "contents: read"]
8+
- ["actions/setup-python", "contents: read"]
9+
- ["actions/setup-java", "contents: read"]
10+
- ["actions/setup-go", "contents: read"]
11+
- ["actions/setup-dotnet", "contents: read"]
12+
- ["actions/labeler", "contents: read"]
13+
- ["actions/labeler", "pull-requests: write"]
14+
- ["actions/attest", "id-token: write"]
15+
- ["actions/attest", "attestations: write"]
16+
# No permissions needed for actions/add-to-project
17+
- ["actions/dependency-review-action", "contents: read"]
18+
- ["actions/attest-sbom", "id-token: write"]
19+
- ["actions/attest-sbom", "attestations: write"]
20+
- ["actions/stale", "contents: write"]
21+
- ["actions/stale", "issues: write"]
22+
- ["actions/stale", "pull-requests: write"]
23+
- ["actions/attest-build-provenance", "id-token: write"]
24+
- ["actions/attest-build-provenance", "attestations: write"]
25+
- ["actions/jekyll-build-pages", "contents: read"]
26+
- ["actions/jekyll-build-pages", "pages: write"]
27+
- ["actions/jekyll-build-pages", "id-token: write"]
28+
- ["actions/publish-action", "contents: write"]
29+
- ["actions/versions-package-tools", "contents: read"]
30+
- ["actions/versions-package-tools", "actions: read"]
31+
- ["actions/reusable-workflows", "contents: read"]
32+
- ["actions/reusable-workflows", "actions: read"]
33+
# TODO: Add permissions for actions/download-artifact
34+
# TODO: Add permissions for actions/upload-artifact
35+
# TODO: Add permissions for actions/cache
36+
37+

actions/ql/src/Security/CWE-275/MissingActionsPermissions.ql

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,36 @@
1414

1515
import actions
1616

17-
from Job job
17+
Step stepInJob(Job job) { result = job.(LocalJob).getAStep() }
18+
19+
bindingset[fullActionSelector]
20+
string versionedAction(string fullActionSelector) {
21+
result = fullActionSelector.substring(0, fullActionSelector.indexOf("@"))
22+
or
23+
not exists(fullActionSelector.indexOf("@")) and
24+
result = fullActionSelector
25+
}
26+
27+
string stepUses(Step step) { result = step.getUses().(ScalarValue).getValue() }
28+
29+
string jobNeedsPersmission(Job job) {
30+
actionsPermissionsDataModel(versionedAction(stepUses(stepInJob(job))), result)
31+
}
32+
33+
string permissionsForJob(Job job) {
34+
result =
35+
"{" + concat(string permission | permission = jobNeedsPersmission(job) | permission, ", ") + "}"
36+
}
37+
38+
from Job job, string permissions
1839
where
1940
not exists(job.getPermissions()) and
2041
not exists(job.getEnclosingWorkflow().getPermissions()) and
2142
// exists a trigger event that is not a workflow_call
2243
exists(Event e |
2344
e = job.getATriggerEvent() and
2445
not e.getName() = "workflow_call"
25-
)
26-
select job, "Actions Job or Workflow does not set permissions"
46+
) and
47+
permissions = permissionsForJob(job)
48+
select job,
49+
"Actions Job or Workflow does not set permissions. A minimal set might be " + permissions
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
on:
2+
workflow_call:
3+
workflow_dispatch:
4+
5+
jobs:
6+
build:
7+
name: Build and test
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: actions/checkout@v2
11+
- uses: actions/jekyll-build-pages
12+
13+
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
| .github/workflows/perms1.yml:6:5:9:32 | Job: build | Actions Job or Workflow does not set permissions |
2-
| .github/workflows/perms2.yml:6:5:10:2 | Job: build | Actions Job or Workflow does not set permissions |
3-
| .github/workflows/perms5.yml:7:5:10:32 | Job: build | Actions Job or Workflow does not set permissions |
1+
| .github/workflows/perms1.yml:6:5:9:32 | Job: build | Actions Job or Workflow does not set permissions. A minimal set might be {contents: read} |
2+
| .github/workflows/perms2.yml:6:5:10:2 | Job: build | Actions Job or Workflow does not set permissions. A minimal set might be {contents: read} |
3+
| .github/workflows/perms5.yml:7:5:10:32 | Job: build | Actions Job or Workflow does not set permissions. A minimal set might be {contents: read} |
4+
| .github/workflows/perms6.yml:7:5:11:39 | Job: build | Actions Job or Workflow does not set permissions. A minimal set might be {contents: read, id-token: write, pages: write} |

0 commit comments

Comments
 (0)