diff --git a/opa/rego/poutine/utils.rego b/opa/rego/poutine/utils.rego index 103543b..2c3a0ea 100644 --- a/opa/rego/poutine/utils.rego +++ b/opa/rego/poutine/utils.rego @@ -56,3 +56,15 @@ empty(xs) if { } else if { count(xs) == 0 } + +workflow_run_parents(pkg, workflow) = parents if { + parent_names = {name | + event := workflow.events[_] + event.name == "workflow_run" + name := event.workflows[_] + } + parents := {parent | + parent := pkg.github_actions_workflows[_] + glob.match(parent_names[_], ["/"], parent.name) + } +} diff --git a/opa/rego/rules/untrusted_checkout_exec.rego b/opa/rego/rules/untrusted_checkout_exec.rego index 45586ef..b6f83be 100644 --- a/opa/rego/rules/untrusted_checkout_exec.rego +++ b/opa/rego/rules/untrusted_checkout_exec.rego @@ -1,7 +1,7 @@ # METADATA # title: Arbitrary Code Execution from Untrusted Code Changes # description: |- -# The workflow appears to checkout untrusted code from a fork +# The workflow appears to checkout untrusted code from a fork # and uses a command that is known to allow code execution. # custom: # level: error @@ -13,6 +13,17 @@ import rego.v1 rule := poutine.rule(rego.metadata.chain()) +github.events contains event if some event in { + "pull_request_target", + "issue_comment", + "workflow_call", +} + +github.workflow_run.parent.events contains event if some event in { + "pull_request_target", + "pull_request", +} + build_github_actions[action] = { "pre-commit/action": "pre-commit", "oxsecurity/megalinter": "megalinter", @@ -60,12 +71,23 @@ _steps_after_untrusted_checkout contains [pkg.purl, workflow.path, s.step] if { pkg := input.packages[_] workflow := pkg.github_actions_workflows[_] - utils.filter_workflow_events(workflow, { - "pull_request_target", - "issue_comment", - "workflow_call", - }) + utils.filter_workflow_events(workflow, github.events) + + pr_checkout := utils.find_pr_checkouts(workflow)[_] + s := utils.workflow_steps_after(pr_checkout)[_] +} + +_steps_after_untrusted_checkout contains [pkg_purl, workflow.path, s.step] if { + [pkg_purl, workflow] := _workflows_runs_from_pr[_] pr_checkout := utils.find_pr_checkouts(workflow)[_] s := utils.workflow_steps_after(pr_checkout)[_] } + +_workflows_runs_from_pr contains [pkg.purl, workflow] if { + pkg := input.packages[_] + workflow := pkg.github_actions_workflows[_] + parent := utils.workflow_run_parents(pkg, workflow)[_] + + utils.filter_workflow_events(parent, github.workflow_run.parent.events) +} diff --git a/scanner/inventory_test.go b/scanner/inventory_test.go index 1709b30..89151f1 100644 --- a/scanner/inventory_test.go +++ b/scanner/inventory_test.go @@ -168,6 +168,15 @@ func TestFindings(t *testing.T) { Details: "Detected usage of `pre-commit`", }, }, + { + RuleId: "untrusted_checkout_exec", + Purl: purl, + Meta: opa.FindingMeta{ + Path: ".github/workflows/workflow_run_valid.yml", + Line: 13, + Details: "Detected usage of `npm`", + }, + }, { RuleId: "default_permissions_on_risky_events", Purl: purl, diff --git a/scanner/scanner_test.go b/scanner/scanner_test.go index 67c6092..500bf7e 100644 --- a/scanner/scanner_test.go +++ b/scanner/scanner_test.go @@ -21,6 +21,8 @@ func TestGithubWorkflows(t *testing.T) { ".github/workflows/valid.yml", ".github/workflows/reusable.yml", ".github/workflows/secrets.yaml", + ".github/workflows/workflow_run_valid.yml", + ".github/workflows/workflow_run_reusable.yml", }) } diff --git a/scanner/testdata/.github/workflows/valid.yml b/scanner/testdata/.github/workflows/valid.yml index e2813ba..a861dd3 100644 --- a/scanner/testdata/.github/workflows/valid.yml +++ b/scanner/testdata/.github/workflows/valid.yml @@ -1,4 +1,4 @@ -name: sample.yml +name: valid.yml on: push: pull_request_target: diff --git a/scanner/testdata/.github/workflows/workflow_run_reusable.yml b/scanner/testdata/.github/workflows/workflow_run_reusable.yml new file mode 100644 index 0000000..3b3723c --- /dev/null +++ b/scanner/testdata/.github/workflows/workflow_run_reusable.yml @@ -0,0 +1,13 @@ +on: + workflow_run: + workflows: [reusable.yml] + +jobs: + pr: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + - run: npm install diff --git a/scanner/testdata/.github/workflows/workflow_run_valid.yml b/scanner/testdata/.github/workflows/workflow_run_valid.yml new file mode 100644 index 0000000..be28fab --- /dev/null +++ b/scanner/testdata/.github/workflows/workflow_run_valid.yml @@ -0,0 +1,13 @@ +on: + workflow_run: + workflows: ["v[a-z]l[i]*.yml"] + +jobs: + pr: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + - run: npm install