|
15 | 15 |
|
16 | 16 | import actions
|
17 | 17 |
|
18 |
| -/** |
19 |
| - * An If node that contains an `actor` check |
20 |
| - */ |
21 |
| -class ActorCheck extends If { |
22 |
| - ActorCheck() { |
| 18 | +/** An If node that contains an actor, user or label check */ |
| 19 | +class ControlCheck extends If { |
| 20 | + ControlCheck() { |
23 | 21 | this.getCondition().regexpMatch(".*github\\.(triggering_)?actor.*") or
|
24 |
| - this.getCondition().regexpMatch(".*github\\.event\\.pull_request\\.user\\.login.*") |
| 22 | + this.getCondition().regexpMatch(".*github\\.event\\.pull_request\\.user\\.login.*") or |
| 23 | + this.getCondition().regexpMatch(".*github\\.event\\.pull_request\\.labels.*") or |
| 24 | + this.getCondition().regexpMatch(".*github\\.event\\.label\\.name.*") |
25 | 25 | }
|
26 | 26 | }
|
27 | 27 |
|
28 |
| -/** |
29 |
| - * An If node that contains a `label` check |
30 |
| - */ |
31 |
| -class LabelCheck extends If { |
32 |
| - LabelCheck() { |
33 |
| - this.getCondition().regexpMatch(".*github\\.event\\.pull_request\\.labels.*") or |
34 |
| - this.getCondition().regexpMatch(".*github\\.event\\.label\\.name.*") |
| 28 | +bindingset[s] |
| 29 | +predicate containsHeadRef(string s) { |
| 30 | + s.matches("%" + |
| 31 | + [ |
| 32 | + "github.event.number", // The pull request number. |
| 33 | + "github.event.pull_request.head.ref", // The ref name of head. |
| 34 | + "github.event.pull_request.head.sha", // The commit SHA of head. |
| 35 | + "github.event.pull_request.id", // The pull request ID. |
| 36 | + "github.event.pull_request.number", // The pull request number. |
| 37 | + "github.event.pull_request.merge_commit_sha", // The SHA of the merge commit. |
| 38 | + "github.head_ref", // The head_ref or source branch of the pull request in a workflow run. |
| 39 | + "github.event.workflow_run.head_branch", // The branch of the head commit. |
| 40 | + "github.event.workflow_run.head_commit.id", // The SHA of the head commit. |
| 41 | + "github.event.workflow_run.head_sha", // The SHA of the head commit. |
| 42 | + "env.GITHUB_HEAD_REF", |
| 43 | + ] + "%") |
| 44 | +} |
| 45 | + |
| 46 | +/** Checkout of a Pull Request HEAD ref */ |
| 47 | +abstract class PRHeadCheckoutStep extends Step { } |
| 48 | + |
| 49 | +/** Checkout of a Pull Request HEAD ref using actions/checkout action */ |
| 50 | +class ActionsCheckout extends PRHeadCheckoutStep instanceof UsesStep { |
| 51 | + ActionsCheckout() { |
| 52 | + this.getCallee() = "actions/checkout" and |
| 53 | + containsHeadRef(this.getArgumentExpr("ref").getExpression()) |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +/** Checkout of a Pull Request HEAD ref using git within a Run step */ |
| 58 | +class GitCheckout extends PRHeadCheckoutStep instanceof Run { |
| 59 | + GitCheckout() { |
| 60 | + exists(string line | |
| 61 | + this.getScript().splitAt("\n") = line and |
| 62 | + line.regexpMatch(".*git\\s+fetch.*") and |
| 63 | + ( |
| 64 | + containsHeadRef(line) |
| 65 | + or |
| 66 | + exists(string varname | |
| 67 | + containsHeadRef(this.getInScopeEnvVarExpr(varname).getExpression()) and |
| 68 | + line.matches("%" + varname + "%") |
| 69 | + ) |
| 70 | + ) |
| 71 | + ) |
35 | 72 | }
|
36 | 73 | }
|
37 | 74 |
|
38 |
| -from Workflow w, LocalJob job, UsesStep checkoutStep |
| 75 | +from Workflow w, PRHeadCheckoutStep checkout |
39 | 76 | where
|
40 |
| - w.hasTriggerEvent("pull_request_target") and |
41 |
| - w.getAJob() = job and |
42 |
| - job.getAStep() = checkoutStep and |
43 |
| - checkoutStep.getCallee() = "actions/checkout" and |
44 |
| - checkoutStep |
45 |
| - .getArgumentExpr("ref") |
46 |
| - .getExpression() |
47 |
| - .matches([ |
48 |
| - "%github.event.pull_request.head.ref%", "%github.event.pull_request.head.sha%", |
49 |
| - "%github.event.pull_request.number%", "%github.event.number%", "%github.head_ref%" |
50 |
| - ]) and |
51 |
| - not exists(ActorCheck check | job.getIf() = check or checkoutStep.getIf() = check) and |
52 |
| - not exists(LabelCheck check | job.getIf() = check or checkoutStep.getIf() = check) |
53 |
| -select checkoutStep, "Potential unsafe checkout of untrusted pull request on 'pull_request_target'." |
| 77 | + w.hasTriggerEvent(["pull_request_target", "issue_comment", "workflow_run"]) and |
| 78 | + w.getAJob().(LocalJob).getAStep() = checkout and |
| 79 | + not exists(ControlCheck check | |
| 80 | + checkout.getIf() = check or checkout.getEnclosingJob().getIf() = check |
| 81 | + ) |
| 82 | +select checkout, "Potential unsafe checkout of untrusted pull request on privileged workflow." |
0 commit comments