Skip to content

Commit 795232e

Browse files
Merge pull request #47 from github/poisonable_config
Move configuration to MaD files
2 parents 24d69f2 + fc81732 commit 795232e

File tree

150 files changed

+224
-148
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

150 files changed

+224
-148
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
private import codeql.actions.ast.internal.Yaml
22
private import codeql.Locations
33
private import codeql.actions.Helper
4-
private import codeql.actions.dataflow.ExternalFlow
4+
private import codeql.actions.config.Config
55

66
/**
77
* Gets the length of each line in the StringValue .
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import ConfigExtensions as Extensions
2+
3+
/**
4+
* MaD models for workflow details
5+
* Fields:
6+
* - path: Path to the workflow file
7+
* - trigger: Trigger for the workflow
8+
* - job: Job name
9+
* - secrets_source: Source of secrets
10+
* - permissions: Permissions for the workflow
11+
* - runner: Runner info for the workflow
12+
*/
13+
predicate workflowDataModel(
14+
string path, string trigger, string job, string secrets_source, string permissions, string runner
15+
) {
16+
Extensions::workflowDataModel(path, trigger, job, secrets_source, permissions, runner)
17+
}
18+
19+
/**
20+
* MaD models for repository details
21+
* Fields:
22+
* - visibility: Visibility of the repository
23+
* - default_branch_name: Default branch name
24+
*/
25+
predicate repositoryDataModel(string visibility, string default_branch_name) {
26+
Extensions::repositoryDataModel(visibility, default_branch_name)
27+
}
28+
29+
/**
30+
* MaD models for context/trigger mapping
31+
* Fields:
32+
* - trigger: Trigger for the workflow
33+
* - context_prefix: Prefix for the context
34+
*/
35+
predicate contextTriggerDataModel(string trigger, string context_prefix) {
36+
Extensions::contextTriggerDataModel(trigger, context_prefix)
37+
}
38+
39+
/**
40+
* MaD models for externally triggerable events
41+
* Fields:
42+
* - event: Event name
43+
*/
44+
predicate externallyTriggerableEventsDataModel(string event) {
45+
Extensions::externallyTriggerableEventsDataModel(event)
46+
}
47+
48+
/**
49+
* MaD models for poisonable commands
50+
* Fields:
51+
* - regexp: Regular expression for matching poisonable commands
52+
*/
53+
predicate poisonableCommandsDataModel(string regexp) {
54+
Extensions::poisonableCommandsDataModel(regexp)
55+
}
56+
57+
/**
58+
* MaD models for poisonable local scripts
59+
* Fields:
60+
* - regexp: Regular expression for matching poisonable local scripts
61+
* - group: Script capture group number for the regular expression
62+
*/
63+
predicate poisonableLocalScriptsDataModel(string regexp, int group) {
64+
Extensions::poisonableLocalScriptsDataModel(regexp, group)
65+
}
66+
67+
/**
68+
* MaD models for poisonable actions
69+
* Fields:
70+
* - action: action name
71+
*/
72+
predicate poisonableActionsDataModel(string action) {
73+
Extensions::poisonableActionsDataModel(action)
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* This module provides extensible predicates for defining MaD models.
3+
*/
4+
5+
/**
6+
* Holds if workflow data model exists for the given parameters.
7+
*/
8+
extensible predicate workflowDataModel(
9+
string path, string trigger, string job, string secrets_source, string permissions, string runner
10+
);
11+
12+
/**
13+
* Holds if repository data model exists for the given parameters.
14+
*/
15+
extensible predicate repositoryDataModel(string visibility, string default_branch_name);
16+
17+
/**
18+
* Holds if a context expression starting with context_prefix is available for a given trigger.
19+
*/
20+
extensible predicate contextTriggerDataModel(string trigger, string context_prefix);
21+
22+
/**
23+
* Holds if a given trigger event can be fired by an external actor.
24+
*/
25+
extensible predicate externallyTriggerableEventsDataModel(string event);
26+
27+
/**
28+
* Holds for strings that match poisonable commands.
29+
*/
30+
extensible predicate poisonableCommandsDataModel(string regexp);
31+
32+
/**
33+
* Holds for strings that match poisonable local scripts.
34+
*/
35+
extensible predicate poisonableLocalScriptsDataModel(string regexp, int group);
36+
37+
/**
38+
* Holds for actions that can be poisoned through local files.
39+
*/
40+
extensible predicate poisonableActionsDataModel(string action);
41+

ql/lib/codeql/actions/dataflow/ExternalFlow.qll

-45
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,6 @@ private import internal.ExternalFlowExtensions as Extensions
22
private import codeql.actions.DataFlow
33
private import actions
44

5-
/**
6-
* MaD models for workflow details
7-
* Fields:
8-
* - path: Path to the workflow file
9-
* - trigger: Trigger for the workflow
10-
* - job: Job name
11-
* - secrets_source: Source of secrets
12-
* - permissions: Permissions for the workflow
13-
* - runner: Runner info for the workflow
14-
*/
15-
predicate workflowDataModel(
16-
string path, string trigger, string job, string secrets_source, string permissions, string runner
17-
) {
18-
Extensions::workflowDataModel(path, trigger, job, secrets_source, permissions, runner)
19-
}
20-
21-
/**
22-
* MaD models for repository details
23-
* Fields:
24-
* - visibility: Visibility of the repository
25-
* - default_branch_name: Default branch name
26-
*/
27-
predicate repositoryDataModel(string visibility, string default_branch_name) {
28-
Extensions::repositoryDataModel(visibility, default_branch_name)
29-
}
30-
31-
/**
32-
* MaD models for context/trigger mapping
33-
* Fields:
34-
* - trigger: Trigger for the workflow
35-
* - context_prefix: Prefix for the context
36-
*/
37-
predicate contextTriggerDataModel(string trigger, string context_prefix) {
38-
Extensions::contextTriggerDataModel(trigger, context_prefix)
39-
}
40-
41-
/**
42-
* MaD models for externally triggerable events
43-
* Fields:
44-
* - event: Event name
45-
*/
46-
predicate externallyTriggerableEventsDataModel(string event) {
47-
Extensions::externallyTriggerableEventsDataModel(event)
48-
}
49-
505
/**
516
* MaD sources
527
* Fields:

ql/lib/codeql/actions/dataflow/FlowSources.qll

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
private import codeql.actions.dataflow.ExternalFlow
21
private import codeql.actions.security.ArtifactPoisoningQuery
2+
private import codeql.actions.config.Config
3+
private import codeql.actions.dataflow.ExternalFlow
34

45
/**
56
* A data flow source.

ql/lib/codeql/actions/dataflow/internal/ExternalFlowExtensions.qll

-22
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,3 @@ extensible predicate actionsSummaryModel(
2222
extensible predicate actionsSinkModel(
2323
string action, string version, string input, string kind, string provenance
2424
);
25-
26-
/**
27-
* Holds if workflow data model exists for the given parameters.
28-
*/
29-
extensible predicate workflowDataModel(
30-
string path, string trigger, string job, string secrets_source, string permissions, string runner
31-
);
32-
33-
/**
34-
* Holds if repository data model exists for the given parameters.
35-
*/
36-
extensible predicate repositoryDataModel(string visibility, string default_branch_name);
37-
38-
/**
39-
* Holds if a context expression starting with context_prefix is available for a given trigger.
40-
*/
41-
extensible predicate contextTriggerDataModel(string trigger, string context_prefix);
42-
43-
/**
44-
* Holds if a given trigger event can be fired by an external actor.
45-
*/
46-
extensible predicate externallyTriggerableEventsDataModel(string event);

ql/lib/codeql/actions/security/ArtifactPoisoningQuery.qll

+2-2
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,8 @@ class ArtifactPoisoningSink extends DataFlow::Node {
254254
poisonable.(UsesStep) = this.asExpr()
255255
) and
256256
(
257-
not poisonable instanceof LocalCommandExecutionRunStep or
258-
poisonable.(LocalCommandExecutionRunStep).getCommand().matches(download.getPath() + "%")
257+
not poisonable instanceof LocalScriptExecutionRunStep or
258+
poisonable.(LocalScriptExecutionRunStep).getCommand().matches(download.getPath() + "%")
259259
)
260260
)
261261
}

ql/lib/codeql/actions/security/CachePoisoningQuery.qll

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import actions
2-
import codeql.actions.dataflow.ExternalFlow
2+
import codeql.actions.config.Config
33

44
string defaultBranchTriggerEvent() {
55
result =

ql/lib/codeql/actions/security/PoisonableSteps.qll

+15-47
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,35 @@
11
import actions
2+
import codeql.actions.config.Config
23

34
abstract class PoisonableStep extends Step { }
45

5-
// source: https://github.com/boostsecurityio/poutine/blob/main/opa/rego/rules/untrusted_checkout_exec.rego#L16
66
private string dangerousActions() {
7-
result =
8-
[
9-
"pre-commit/action", "oxsecurity/megalinter", "bridgecrewio/checkov-action",
10-
"ruby/setup-ruby", "actions/jekyll-build-pages"
11-
]
7+
exists(string action |
8+
poisonableActionsDataModel(action) and
9+
result = action
10+
)
1211
}
1312

1413
class DangerousActionUsesStep extends PoisonableStep, UsesStep {
1514
DangerousActionUsesStep() { this.getCallee() = dangerousActions() }
1615
}
1716

18-
// source: https://github.com/boostsecurityio/poutine/blob/main/opa/rego/rules/untrusted_checkout_exec.rego#L23
19-
private string dangerousCommands() {
20-
result =
21-
[
22-
"npm i(nstall)?(\\b|$)", "npm run ", "yarn ", "npm ci(\\b|$)", "make ", "terraform plan",
23-
"terraform apply", "gomplate ", "pre-commit run", "pre-commit install", "go generate",
24-
"msbuild ", "mvn ", "gradle ", "bundle install", "bundle exec ", "^ant ", "mkdocs build",
25-
"pytest", "pip install -r ", "pip install --requirement", "java -jar ", "poetry install",
26-
"poetry run", "cargo "
27-
]
28-
}
29-
30-
class BuildRunStep extends PoisonableStep, Run {
31-
BuildRunStep() {
32-
exists(
33-
this.getScript().splitAt("\n").trim().regexpFind("([^a-z]|^)" + dangerousCommands(), _, _)
17+
class PoisonableCommandStep extends PoisonableStep, Run {
18+
PoisonableCommandStep() {
19+
exists(string regexp |
20+
poisonableCommandsDataModel(regexp) and
21+
exists(this.getScript().splitAt("\n").trim().regexpFind("([^a-z]|^)" + regexp, _, _))
3422
)
3523
}
3624
}
3725

38-
bindingset[cmdRegexp]
39-
string wrapLocalCmd(string cmdRegexp) { result = "(^|;\\s*|\\s+)" + cmdRegexp + "(\\s+|;|$)" }
40-
41-
class LocalCommandExecutionRunStep extends PoisonableStep, Run {
26+
class LocalScriptExecutionRunStep extends PoisonableStep, Run {
4227
string cmd;
4328

44-
LocalCommandExecutionRunStep() {
45-
// Heuristic:
46-
exists(string line | line = this.getScript().splitAt("\n").trim() |
47-
// ./xxxx
48-
// TODO: It could also be in the form of `dir/cmd`
49-
cmd = line.regexpCapture(wrapLocalCmd("\\.\\/(.*)"), 2)
50-
or
51-
// sh xxxx
52-
cmd = line.regexpCapture(wrapLocalCmd("(ba|z|fi)?sh\\s+(.*)"), 3)
53-
or
54-
// node xxxx.js
55-
cmd = line.regexpCapture(wrapLocalCmd("node\\s+(.*)(\\.js|\\.ts)"), 2)
56-
or
57-
// python xxxx.py
58-
cmd = line.regexpCapture(wrapLocalCmd("python\\s+(.*)\\.py"), 2)
59-
or
60-
// ruby xxxx.rb
61-
cmd = line.regexpCapture(wrapLocalCmd("ruby\\s+(.*)\\.rb"), 2)
62-
or
63-
// go xxxx.go
64-
cmd = line.regexpCapture(wrapLocalCmd("go\\s+(.*)\\.go"), 2)
29+
LocalScriptExecutionRunStep() {
30+
exists(string line, string regexp, int group | line = this.getScript().splitAt("\n").trim() |
31+
poisonableLocalScriptsDataModel(regexp, group) and
32+
cmd = line.regexpCapture(regexp, group)
6533
)
6634
}
6735

ql/lib/codeql/actions/security/SelfHostedQuery.qll

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import actions
2-
import codeql.actions.dataflow.ExternalFlow
2+
import codeql.actions.config.Config
33

44
bindingset[runner]
55
predicate isGithubHostedRunner(string runner) {

ql/lib/ext/workflow-models/workflow-models.yml ql/lib/ext/config/context_event_map.yml

+1-24
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
11
extensions:
2-
- addsTo:
3-
pack: github/actions-all
4-
extensible: repositoryDataModel
5-
data: []
6-
- addsTo:
7-
pack: github/actions-all
8-
extensible: workflowDataModel
9-
data: []
102
- addsTo:
113
pack: github/actions-all
124
extensible: contextTriggerDataModel
@@ -54,19 +46,4 @@ extensions:
5446
- ["workflow_call", "github.event.review"]
5547
- ["workflow_call", "github.event.workflow"]
5648
- ["workflow_call", "github.event.workflow_run"]
57-
- addsTo:
58-
pack: github/actions-all
59-
extensible: externallyTriggerableEventsDataModel
60-
data:
61-
- ["discussion"]
62-
- ["discussion_comment"]
63-
- ["fork"]
64-
- ["issue_comment"]
65-
- ["issues"]
66-
- ["pull_request"]
67-
- ["pull_request_comment"]
68-
- ["pull_request_review"]
69-
- ["pull_request_review_comment"]
70-
- ["pull_request_target"]
71-
- ["workflow_run"] # depending on trigger workflow
72-
- ["workflow_call"] # depending on caller
49+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
extensions:
2+
- addsTo:
3+
pack: github/actions-all
4+
extensible: externallyTriggerableEventsDataModel
5+
data:
6+
- ["discussion"]
7+
- ["discussion_comment"]
8+
- ["fork"]
9+
- ["issue_comment"]
10+
- ["issues"]
11+
- ["pull_request"]
12+
- ["pull_request_comment"]
13+
- ["pull_request_review"]
14+
- ["pull_request_review_comment"]
15+
- ["pull_request_target"]
16+
- ["workflow_run"] # depending on trigger workflow
17+
- ["workflow_call"] # depending on caller
18+

0 commit comments

Comments
 (0)