1
1
import actions
2
2
3
+ string any_relevant_category ( ) {
4
+ result =
5
+ [
6
+ "untrusted-checkout" , "output-clobbering" , "envpath-injection" , "envvar-injection" ,
7
+ "command-injection" , "argument-injection" , "code-injection" , "cache-poisoning" ,
8
+ "untrusted-checkout-toctou" , "artifact-poisoning"
9
+ ]
10
+ }
11
+
12
+ string any_non_toctou_category ( ) {
13
+ result = any_relevant_category ( ) and not result = "untrusted-checkout-toctou"
14
+ }
15
+
16
+ string any_relevant_event ( ) {
17
+ result =
18
+ [
19
+ "pull_request_target" ,
20
+ "issue_comment" ,
21
+ "pull_request_comment" ,
22
+ "workflow_run" ,
23
+ "issues" ,
24
+ "fork" ,
25
+ "watch" ,
26
+ "discussion_comment" ,
27
+ "discussion"
28
+ ]
29
+ }
30
+
3
31
/** An If node that contains an actor, user or label check */
4
32
abstract class ControlCheck extends AstNode {
5
33
ControlCheck ( ) {
6
34
this instanceof If or
7
35
this instanceof Environment or
8
- this instanceof UsesStep
36
+ this instanceof UsesStep or
37
+ this instanceof Run
9
38
}
10
39
11
- predicate protects ( Step step , Event event ) {
40
+ predicate protects ( Step step , Event event , string category ) {
12
41
event .getEnclosingWorkflow ( ) = step .getEnclosingWorkflow ( ) and
13
- this .getAProtectedEvent ( ) = event . getName ( ) and
14
- this .dominates ( step )
42
+ this .dominates ( step ) and
43
+ this .protectsCategoryAndEvent ( category , event . getName ( ) )
15
44
}
16
45
17
46
predicate dominates ( Step step ) {
@@ -30,80 +59,71 @@ abstract class ControlCheck extends AstNode {
30
59
step .getEnclosingJob ( ) .getANeededJob ( ) .getEnvironment ( ) = this
31
60
)
32
61
or
33
- this .( UsesStep ) .getAFollowingStep ( ) = step
62
+ this .( Step ) .getAFollowingStep ( ) = step
34
63
}
35
64
36
- abstract string getAProtectedEvent ( ) ;
37
-
38
- abstract boolean protectsAgainstRefMutationAttacks ( ) ;
65
+ abstract predicate protectsCategoryAndEvent ( string category , string event ) ;
39
66
}
40
67
41
68
abstract class AssociationCheck extends ControlCheck {
42
- // checks who you are (identity)
43
- // association checks are effective against pull requests since they can control who is making the PR
44
- // they are not effective against issue_comment since the author of the comment may not be the same as the author of the PR
45
- // someone entitled to trigger the workflow with a comment, may no detect a malicious comment, or the comment may mutate after approval
46
- override string getAProtectedEvent ( ) { result = [ "pull_request" , "pull_request_target" ] }
47
-
48
- override boolean protectsAgainstRefMutationAttacks ( ) { result = true }
69
+ // Checks if the actor is a COLLABORATOR of the repo
70
+ // - they are effective against pull requests and workflow_run (since these are triggered by pull_requests) since they can control who is making the PR
71
+ // - they are not effective against issue_comment since the author of the comment may not be the same as the author of the PR
72
+ override predicate protectsCategoryAndEvent ( string category , string event ) {
73
+ event = [ "pull_request_target" , "workflow_run" ] and category = any_relevant_category ( )
74
+ }
49
75
}
50
76
51
77
abstract class ActorCheck extends ControlCheck {
52
- // checks who you are (identity)
53
- // actor checks are effective against pull requests since they can control who is making the PR
54
- // they are not effective against issue_comment since the author of the comment may not be the same as the author of the PR
55
- // someone entitled to trigger the workflow with a comment, may no detect a malicious comment, or the comment may mutate after approval
56
- override string getAProtectedEvent ( ) { result = [ "pull_request" , "pull_request_target" ] }
57
-
58
- override boolean protectsAgainstRefMutationAttacks ( ) { result = true }
78
+ // checks for a specific actor
79
+ // - they are effective against pull requests and workflow_run (since these are triggered by pull_requests) since they can control who is making the PR
80
+ // - they are not effective against issue_comment since the author of the comment may not be the same as the author of the PR
81
+ override predicate protectsCategoryAndEvent ( string category , string event ) {
82
+ event = [ "pull_request_target" , "workflow_run" ] and category = any_relevant_category ( )
83
+ }
59
84
}
60
85
61
86
abstract class RepositoryCheck extends ControlCheck {
62
- // repository checks are effective against pull requests since they can control where the code is coming from
63
- // they are not effective against issue_comment since the repository will always be the same
64
- // who you are (identity)
65
- override string getAProtectedEvent ( ) { result = [ "pull_request" , "pull_request_target" ] }
66
-
67
- override boolean protectsAgainstRefMutationAttacks ( ) { result = true }
87
+ // checks that the origin of the code is the same as the repository.
88
+ // for pull_requests, that means that it triggers only on local branches or repos from the same org
89
+ // - they are effective against pull requests/workflow_run since they can control where the code is coming from
90
+ // - they are not effective against issue_comment since the repository will always be the same
91
+ override predicate protectsCategoryAndEvent ( string category , string event ) {
92
+ event = [ "pull_request_target" , "workflow_run" ] and category = any_relevant_category ( )
93
+ }
68
94
}
69
95
70
96
abstract class PermissionCheck extends ControlCheck {
71
- // permission checks are effective against pull requests since they can control who can make changes
72
- // they are not effective against issue_comment since the author of the comment may not be the same as the author of the PR
73
- // someone entitled to trigger the workflow with a comment, may no detect a malicious comment, or the comment may mutate after approval
74
- // who you are (identity)
75
- override string getAProtectedEvent ( ) { result = [ "pull_request " , "pull_request_target" ] }
76
-
77
- override boolean protectsAgainstRefMutationAttacks ( ) { result = true }
97
+ // checks that the actor has a specific permission level
98
+ // - they are effective against pull requests/workflow_run since they can control who can make changes
99
+ // - they are not effective against issue_comment since the author of the comment may not be the same as the author of the PR
100
+ override predicate protectsCategoryAndEvent ( string category , string event ) {
101
+ event = [ "pull_request_target " , "workflow_run" , "issue_comment" ] and
102
+ category = any_relevant_category ( )
103
+ }
78
104
}
79
105
80
106
abstract class LabelCheck extends ControlCheck {
81
- // does it protect injection attacks but not pwn requests?
82
- // pwn requests are susceptible to checkout of mutable code
83
- // but injection attacks are not, although a branch name can be changed after approval and perhaps also some other things
84
- // they do actually protext against untrusted code execution (sha)
85
- // what you have (approval)
86
- // TODO: A check should be a combination of:
87
- // - event type (pull_request, issue_comment, etc)
88
- // - category (untrusted mutable code, untrusted immutable code, code injection, etc)
89
- // - we dont know this unless we pass category to inPrivilegedContext and into ControlCheck.protects
90
- // - we can decide if a control check is effective based only on the ast node
91
- override string getAProtectedEvent ( ) { result = [ "pull_request" , "pull_request_target" ] }
92
-
93
- // ref can be mutated after approval
94
- override boolean protectsAgainstRefMutationAttacks ( ) { result = false }
107
+ // checks if the issue/pull_request is labeled, which implies that it could have been approved
108
+ // - they dont protect against mutation attacks
109
+ override predicate protectsCategoryAndEvent ( string category , string event ) {
110
+ event = [ "pull_request_target" , "workflow_run" ] and category = any_non_toctou_category ( )
111
+ }
95
112
}
96
113
97
114
class EnvironmentCheck extends ControlCheck instanceof Environment {
98
115
// Environment checks are not effective against any mutable attacks
99
- // they do actually protext against untrusted code execution (sha)
100
- // what you have (approval)
101
- EnvironmentCheck ( ) { any ( ) }
102
-
103
- override string getAProtectedEvent ( ) { result = [ "pull_request" , "pull_request_target" ] }
116
+ // they do actually protect against untrusted code execution (sha)
117
+ override predicate protectsCategoryAndEvent ( string category , string event ) {
118
+ event = [ "pull_request_target" , "workflow_run" ] and category = any_non_toctou_category ( )
119
+ }
120
+ }
104
121
105
- // ref can be mutated after approval
106
- override boolean protectsAgainstRefMutationAttacks ( ) { result = false }
122
+ abstract class CommentVsHeadDateCheck extends ControlCheck {
123
+ override predicate protectsCategoryAndEvent ( string category , string event ) {
124
+ // by itself, this check is not effective against any attacks
125
+ none ( )
126
+ }
107
127
}
108
128
109
129
/* Specific implementations of control checks */
@@ -184,6 +204,12 @@ class AssociationActionCheck extends AssociationCheck instanceof UsesStep {
184
204
185
205
class PermissionActionCheck extends PermissionCheck instanceof UsesStep {
186
206
PermissionActionCheck ( ) {
207
+ this .getCallee ( ) = "sushichop/action-repository-permission" and
208
+ this .getArgument ( "required-permission" ) = [ "write" , "admin" ]
209
+ or
210
+ this .getCallee ( ) = "prince-chrismc/check-actor-permissions-action" and
211
+ this .getArgument ( "permission" ) = [ "write" , "admin" ]
212
+ or
187
213
this .getCallee ( ) = "lannonbr/repo-permission-check-action" and
188
214
this .getArgument ( "permission" ) = [ "write" , "admin" ]
189
215
or
@@ -195,3 +221,13 @@ class PermissionActionCheck extends PermissionCheck instanceof UsesStep {
195
221
)
196
222
}
197
223
}
224
+
225
+ class BashCommentVsHeadDateCheck extends CommentVsHeadDateCheck , Run {
226
+ BashCommentVsHeadDateCheck ( ) {
227
+ exists ( string line |
228
+ line = this .getScript ( ) .splitAt ( "\n" ) and
229
+ line .toLowerCase ( )
230
+ .regexpMatch ( ".*date\\s+-d.*(commit_at|pushed_at|comment_at|commented_at).*date\\s+-d.*(commit_at|pushed_at|comment_at|commented_at).*" )
231
+ )
232
+ }
233
+ }
0 commit comments