11import actions
22
3- string any_relevant_category ( ) {
3+ string any_category ( ) {
44 result =
55 [
66 "untrusted-checkout" , "output-clobbering" , "envpath-injection" , "envvar-injection" ,
77 "command-injection" , "argument-injection" , "code-injection" , "cache-poisoning" ,
8- "untrusted-checkout-toctou" , "artifact-poisoning"
8+ "untrusted-checkout-toctou" , "artifact-poisoning" , "artifact-poisoning-toctou"
99 ]
1010}
1111
12- string any_non_toctou_category ( ) {
13- result = any_relevant_category ( ) and not result = "untrusted-checkout-toctou"
12+ string non_toctou_category ( ) {
13+ result = any_category ( ) and not result = "untrusted-checkout-toctou"
1414}
1515
16- string any_relevant_event ( ) {
16+ string toctou_category ( ) { result = [ "untrusted-checkout-toctou" , "artifact-poisoning-toctou" ] }
17+
18+ string any_event ( ) { result = actor_not_attacker_event ( ) or result = actor_is_attacker_event ( ) }
19+
20+ string actor_is_attacker_event ( ) {
1721 result =
1822 [
23+ // actor and attacker have to be the same
1924 "pull_request_target" ,
20- "issue_comment" ,
21- "pull_request_comment" ,
2225 "workflow_run" ,
26+ "discussion_comment" ,
27+ "discussion" ,
2328 "issues" ,
2429 "fork" ,
25- "watch" ,
26- "discussion_comment" ,
27- "discussion"
30+ "watch"
31+ ]
32+ }
33+
34+ string actor_not_attacker_event ( ) {
35+ result =
36+ [
37+ // actor and attacker can be different
38+ // actor may be a collaborator, but the attacker is may be the author of the PR that gets commented
39+ // therefore it may be vulnerable to TOCTOU races where the actor reviews one thing and the attacker changes it
40+ "issue_comment" ,
41+ "pull_request_comment" ,
2842 ]
2943}
3044
@@ -81,7 +95,9 @@ abstract class AssociationCheck extends ControlCheck {
8195 // - 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
8296 // - they are not effective against issue_comment since the author of the comment may not be the same as the author of the PR
8397 override predicate protectsCategoryAndEvent ( string category , string event ) {
84- event = [ "pull_request_target" , "workflow_run" ] and category = any_relevant_category ( )
98+ event = actor_is_attacker_event ( ) and category = any_category ( )
99+ or
100+ event = actor_not_attacker_event ( ) and category = non_toctou_category ( )
85101 }
86102}
87103
@@ -90,7 +106,9 @@ abstract class ActorCheck extends ControlCheck {
90106 // - 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
91107 // - they are not effective against issue_comment since the author of the comment may not be the same as the author of the PR
92108 override predicate protectsCategoryAndEvent ( string category , string event ) {
93- event = [ "pull_request_target" , "workflow_run" ] and category = any_relevant_category ( )
109+ event = actor_is_attacker_event ( ) and category = any_category ( )
110+ or
111+ event = actor_not_attacker_event ( ) and category = non_toctou_category ( )
94112 }
95113}
96114
@@ -106,31 +124,36 @@ abstract class PermissionCheck extends ControlCheck {
106124 // - they are effective against pull requests/workflow_run since they can control who can make changes
107125 // - they are not effective against issue_comment since the author of the comment may not be the same as the author of the PR
108126 override predicate protectsCategoryAndEvent ( string category , string event ) {
109- event = [ "pull_request_target" , "workflow_run" , "issue_comment" ] and
110- category = any_relevant_category ( )
127+ event = actor_is_attacker_event ( ) and category = any_category ( )
128+ or
129+ event = actor_not_attacker_event ( ) and category = non_toctou_category ( )
111130 }
112131}
113132
114133abstract class LabelCheck extends ControlCheck {
115134 // checks if the issue/pull_request is labeled, which implies that it could have been approved
116135 // - they dont protect against mutation attacks
117136 override predicate protectsCategoryAndEvent ( string category , string event ) {
118- event = [ "pull_request_target" , "workflow_run" ] and category = any_non_toctou_category ( )
137+ event = actor_is_attacker_event ( ) and category = any_category ( )
138+ or
139+ event = actor_not_attacker_event ( ) and category = non_toctou_category ( )
119140 }
120141}
121142
122143class EnvironmentCheck extends ControlCheck instanceof Environment {
123144 // Environment checks are not effective against any mutable attacks
124145 // they do actually protect against untrusted code execution (sha)
125146 override predicate protectsCategoryAndEvent ( string category , string event ) {
126- event = [ "pull_request_target" , "workflow_run" ] and category = any_non_toctou_category ( )
147+ event = actor_is_attacker_event ( ) and category = any_category ( )
148+ or
149+ event = actor_not_attacker_event ( ) and category = non_toctou_category ( )
127150 }
128151}
129152
130153abstract class CommentVsHeadDateCheck extends ControlCheck {
131154 override predicate protectsCategoryAndEvent ( string category , string event ) {
132155 // by itself, this check is not effective against any attacks
133- none ( )
156+ event = actor_not_attacker_event ( ) and category = toctou_category ( )
134157 }
135158}
136159
@@ -187,7 +210,7 @@ class PullRequestTargetRepositoryIfCheck extends RepositoryCheck instanceof If {
187210 }
188211
189212 override predicate protectsCategoryAndEvent ( string category , string event ) {
190- event = "pull_request_target" and category = any_relevant_category ( )
213+ event = "pull_request_target" and category = any_category ( )
191214 }
192215}
193216
@@ -205,7 +228,7 @@ class WorkflowRunRepositoryIfCheck extends RepositoryCheck instanceof If {
205228 }
206229
207230 override predicate protectsCategoryAndEvent ( string category , string event ) {
208- event = "workflow_run" and category = any_relevant_category ( )
231+ event = "workflow_run" and category = any_category ( )
209232 }
210233}
211234
@@ -250,6 +273,9 @@ class PermissionActionCheck extends PermissionCheck instanceof UsesStep {
250273 not exists ( this .getArgument ( "permission-level" ) ) or
251274 this .getArgument ( "permission-level" ) = [ "write" , "admin" ]
252275 )
276+ or
277+ this .getCallee ( ) = "actions/github-script" and
278+ this .getArgument ( "script" ) .splitAt ( "\n" ) .matches ( "%getCollaboratorPermissionLevel%" )
253279 }
254280}
255281
0 commit comments