@@ -308,19 +308,22 @@ class CompositeActionImpl extends AstNodeImpl, TCompositeAction {
308
308
LocalJobImpl getACallerJob ( ) { result = this .getACallerStep ( ) .getEnclosingJob ( ) }
309
309
310
310
UsesStepImpl getACallerStep ( ) {
311
- exists ( UsesStepImpl caller , string gwf_path , string path |
312
- // the workflow files may not be rooted in the parent directory of .github/workflows
313
- // extract the offset so we can remove it from the action path
314
- gwf_path =
315
- caller
316
- .getLocation ( )
311
+ exists ( DataFlow:: CallNode call |
312
+ call .getCalleeNode ( ) = this and
313
+ result = call .getCfgNode ( ) .getAstNode ( )
314
+ )
315
+ }
316
+
317
+ string getResolvedPath ( ) {
318
+ result =
319
+ [ "" , "./" ] +
320
+ this .getLocation ( )
317
321
.getFile ( )
318
322
.getRelativePath ( )
319
- .prefix ( caller .getLocation ( ) .getFile ( ) .getRelativePath ( ) .indexOf ( ".github/workflows/" ) ) and
320
- path = this .getLocation ( ) .getFile ( ) .getRelativePath ( ) .replaceAll ( gwf_path , "" ) and
321
- caller .getCallee ( ) = [ "" , "./" ] + path .prefix ( path .indexOf ( [ "/action.yml" , "/action.yaml" ] ) ) and
322
- result = caller
323
- )
323
+ .replaceAll ( getRepoRoot ( ) , "" )
324
+ .replaceAll ( "/action.yml" , "" )
325
+ .replaceAll ( "/action.yaml" , "" )
326
+ .replaceAll ( ".github/reusable_workflows/" , "" )
324
327
}
325
328
326
329
private predicate hasExplicitSecretAccess ( ) {
@@ -352,6 +355,8 @@ class CompositeActionImpl extends AstNodeImpl, TCompositeAction {
352
355
)
353
356
}
354
357
358
+ EventImpl getATriggerEvent ( ) { result = this .getACallerJob ( ) .getATriggerEvent ( ) }
359
+
355
360
/** Holds if the action is privileged and externally triggerable. */
356
361
predicate isPrivilegedExternallyTriggerable ( ) {
357
362
// the action is externally triggerable
@@ -416,6 +421,14 @@ class ReusableWorkflowImpl extends AstNodeImpl, WorkflowImpl {
416
421
417
422
override AstNodeImpl getAChildNode ( ) { result .getNode ( ) = n .getAChildNode * ( ) }
418
423
424
+ override EventImpl getATriggerEvent ( ) {
425
+ // The trigger event for a reusable workflow is the trigger event of the caller workflow
426
+ this .getACaller ( ) .getEnclosingWorkflow ( ) .getOn ( ) .getAnEvent ( ) = result
427
+ or
428
+ // or the trigger event of the workflow if it has any other than workflow_call
429
+ this .getOn ( ) .getAnEvent ( ) = result and not result .getName ( ) = "workflow_call"
430
+ }
431
+
419
432
OutputsImpl getOutputs ( ) { result .getNode ( ) = workflow_call .( YamlMapping ) .lookup ( "outputs" ) }
420
433
421
434
ExpressionImpl getAnOutputExpr ( ) { result = this .getOutputs ( ) .getAnOutputExpr ( ) }
@@ -439,6 +452,16 @@ class ReusableWorkflowImpl extends AstNodeImpl, WorkflowImpl {
439
452
result = call .getCfgNode ( ) .getAstNode ( )
440
453
)
441
454
}
455
+
456
+ string getResolvedPath ( ) {
457
+ result =
458
+ [ "" , "./" ] +
459
+ this .getLocation ( )
460
+ .getFile ( )
461
+ .getRelativePath ( )
462
+ .replaceAll ( getRepoRoot ( ) , "" )
463
+ .replaceAll ( ".github/reusable_workflows/" , "" )
464
+ }
442
465
}
443
466
444
467
class InputsImpl extends AstNodeImpl , TInputsNode {
@@ -796,12 +819,16 @@ class JobImpl extends AstNodeImpl, TJobNode {
796
819
StrategyImpl getStrategy ( ) { result .getNode ( ) = n .lookup ( "strategy" ) }
797
820
798
821
/** Gets the trigger event that starts this workflow. */
799
- EventImpl getATriggerEvent ( ) { result = this .getEnclosingWorkflow ( ) .getATriggerEvent ( ) }
822
+ EventImpl getATriggerEvent ( ) {
823
+ if this .getEnclosingWorkflow ( ) instanceof ReusableWorkflowImpl
824
+ then
825
+ result = this .getEnclosingWorkflow ( ) .( ReusableWorkflowImpl ) .getACaller ( ) .getATriggerEvent ( )
826
+ or
827
+ result = this .getEnclosingWorkflow ( ) .getATriggerEvent ( ) and
828
+ not result .getName ( ) = "workflow_call"
829
+ else result = this .getEnclosingWorkflow ( ) .getATriggerEvent ( )
830
+ }
800
831
801
- // private predicate hasSingleTrigger(string trigger) {
802
- // this.getATriggerEvent().getName() = trigger and
803
- // count(this.getATriggerEvent()) = 1
804
- // }
805
832
/** Gets the runs-on field of the job. */
806
833
string getARunsOnLabel ( ) {
807
834
exists ( ScalarValueImpl lbl , YamlMappingLikeNode runson |
@@ -839,9 +866,8 @@ class JobImpl extends AstNodeImpl, TJobNode {
839
866
)
840
867
}
841
868
842
- private predicate hasExplicitWritePermission ( ) {
843
- // the job has an explicit write permission
844
- this .getPermissions ( ) .getAPermission ( ) .matches ( "%write" )
869
+ private predicate hasExplicitNonePermission ( ) {
870
+ exists ( this .getPermissions ( ) ) and not exists ( this .getPermissions ( ) .getAPermission ( ) )
845
871
}
846
872
847
873
private predicate hasExplicitReadPermission ( ) {
@@ -850,15 +876,57 @@ class JobImpl extends AstNodeImpl, TJobNode {
850
876
not this .getPermissions ( ) .getAPermission ( ) .matches ( "%write" )
851
877
}
852
878
853
- private predicate hasImplicitWritePermission ( ) {
879
+ private predicate hasExplicitWritePermission ( ) {
854
880
// the job has an explicit write permission
855
- this .getEnclosingWorkflow ( ) .getPermissions ( ) .getAPermission ( ) .matches ( "%write" )
881
+ this .getPermissions ( ) .getAPermission ( ) .matches ( "%write" )
882
+ }
883
+
884
+ private predicate hasImplicitNonePermission ( ) {
885
+ not exists ( this .getPermissions ( ) ) and
886
+ exists ( this .getEnclosingWorkflow ( ) .getPermissions ( ) ) and
887
+ not exists ( this .getEnclosingWorkflow ( ) .getPermissions ( ) .getAPermission ( ) )
888
+ or
889
+ not exists ( this .getPermissions ( ) ) and
890
+ not exists ( this .getEnclosingWorkflow ( ) .getPermissions ( ) ) and
891
+ exists ( this .getEnclosingWorkflow ( ) .( ReusableWorkflowImpl ) .getACaller ( ) .getPermissions ( ) ) and
892
+ not exists (
893
+ this .getEnclosingWorkflow ( )
894
+ .( ReusableWorkflowImpl )
895
+ .getACaller ( )
896
+ .getPermissions ( )
897
+ .getAPermission ( )
898
+ )
856
899
}
857
900
858
901
private predicate hasImplicitReadPermission ( ) {
859
902
// the job has not an explicit write permission
903
+ not exists ( this .getPermissions ( ) ) and
860
904
exists ( this .getEnclosingWorkflow ( ) .getPermissions ( ) .getAPermission ( ) ) and
861
905
not this .getEnclosingWorkflow ( ) .getPermissions ( ) .getAPermission ( ) .matches ( "%write" )
906
+ or
907
+ not exists ( this .getPermissions ( ) ) and
908
+ not exists ( this .getEnclosingWorkflow ( ) .getPermissions ( ) ) and
909
+ this .getEnclosingWorkflow ( )
910
+ .( ReusableWorkflowImpl )
911
+ .getACaller ( )
912
+ .getPermissions ( )
913
+ .getAPermission ( )
914
+ .matches ( "%read" )
915
+ }
916
+
917
+ private predicate hasImplicitWritePermission ( ) {
918
+ // the job has an explicit write permission
919
+ not exists ( this .getPermissions ( ) ) and
920
+ this .getEnclosingWorkflow ( ) .getPermissions ( ) .getAPermission ( ) .matches ( "%write" )
921
+ or
922
+ not exists ( this .getPermissions ( ) ) and
923
+ not exists ( this .getEnclosingWorkflow ( ) .getPermissions ( ) ) and
924
+ this .getEnclosingWorkflow ( )
925
+ .( ReusableWorkflowImpl )
926
+ .getACaller ( )
927
+ .getPermissions ( )
928
+ .getAPermission ( )
929
+ .matches ( "%write" )
862
930
}
863
931
864
932
private predicate hasRuntimeData ( ) {
@@ -917,6 +985,8 @@ class JobImpl extends AstNodeImpl, TJobNode {
917
985
// and the job is not explicitly non-privileged
918
986
not (
919
987
(
988
+ this .hasExplicitNonePermission ( ) or
989
+ this .hasImplicitNonePermission ( ) or
920
990
this .hasExplicitReadPermission ( ) or
921
991
this .hasImplicitReadPermission ( )
922
992
) and
@@ -1174,15 +1244,6 @@ abstract class UsesImpl extends AstNodeImpl {
1174
1244
}
1175
1245
}
1176
1246
1177
- /**
1178
- * Gets a regular expression that parses an `owner/repo@version` reference within a `uses` field in an Actions job step.
1179
- * The capture groups are:
1180
- * 1: The owner of the repository where the Action comes from, e.g. `actions` in `actions/checkout@v2`
1181
- * 2: The name of the repository where the Action comes from, e.g. `checkout` in `actions/checkout@v2`.
1182
- * 3: The version reference used when checking out the Action, e.g. `v2` in `actions/checkout@v2`.
1183
- */
1184
- private string usesParser ( ) { result = "([^/]+)/([^/@]+)@(.+)" }
1185
-
1186
1247
/** A Uses step represents a call to an action that is defined in a GitHub repository. */
1187
1248
class UsesStepImpl extends StepImpl , UsesImpl {
1188
1249
YamlScalar u ;
@@ -1194,19 +1255,14 @@ class UsesStepImpl extends StepImpl, UsesImpl {
1194
1255
/** Gets the owner and name of the repository where the Action comes from, e.g. `actions/checkout` in `actions/checkout@v2`. */
1195
1256
override string getCallee ( ) {
1196
1257
if u .getValue ( ) .indexOf ( "@" ) > 0
1197
- then
1198
- result =
1199
- (
1200
- u .getValue ( ) .regexpCapture ( usesParser ( ) , 1 ) + "/" +
1201
- u .getValue ( ) .regexpCapture ( usesParser ( ) , 2 )
1202
- ) .toLowerCase ( )
1258
+ then result = u .getValue ( ) .prefix ( u .getValue ( ) .indexOf ( "@" ) )
1203
1259
else result = u .getValue ( )
1204
1260
}
1205
1261
1206
1262
override ScalarValueImpl getCalleeNode ( ) { result .getNode ( ) = u }
1207
1263
1208
1264
/** Gets the version reference used when checking out the Action, e.g. `v2` in `actions/checkout@v2`. */
1209
- override string getVersion ( ) { result = u .getValue ( ) .regexpCapture ( usesParser ( ) , 3 ) }
1265
+ override string getVersion ( ) { result = u .getValue ( ) .suffix ( u . getValue ( ) . indexOf ( "@" ) + 1 ) }
1210
1266
1211
1267
override string toString ( ) {
1212
1268
if exists ( this .getId ( ) ) then result = "Uses Step: " + this .getId ( ) else result = "Uses Step"
0 commit comments