From 21a37e28325e5b05222ad1c325f65d7d455e3924 Mon Sep 17 00:00:00 2001 From: Tyagiquamar Date: Mon, 12 Oct 2026 18:19:13 +0530 Subject: [PATCH 1/2] fix(rules): ignore bash scripts under RUNNER_TEMP in untrusted_checkout_exec Steps that run shell scripts from RUNNER_TEMP are not executing checked-out workspace code. Suppress untrusted_checkout_exec when the run step references paths under RUNNER_TEMP, fixing false positives such as agentic workflow activation jobs (fixes #457). Signed-off-by: Abhipsita-coder <76418226+Abhipsita-coder@users.noreply.github.com> --- .../en/rules/untrusted_checkout_exec.md | 2 +- opa/rego/rules/untrusted_checkout_exec.rego | 6 +++ scanner/runner_temp_test.go | 41 +++++++++++++++++++ .../.github/workflows/runner_temp_script.yml | 19 +++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 scanner/runner_temp_test.go create mode 100644 scanner/testdata/.github/workflows/runner_temp_script.yml diff --git a/docs/content/en/rules/untrusted_checkout_exec.md b/docs/content/en/rules/untrusted_checkout_exec.md index bf987e9..2ebd859 100644 --- a/docs/content/en/rules/untrusted_checkout_exec.md +++ b/docs/content/en/rules/untrusted_checkout_exec.md @@ -10,7 +10,7 @@ severity: error The workflow appears to checkout untrusted code from a fork and uses a command that is known to allow code execution. -Supported `actions/checkout` releases refuse fork pull request code by default for `pull_request_target` and pull-request-originated `workflow_run` events. Poutine suppresses this finding when that guard applies. The finding remains when the checkout uses `allow-unsafe-pr-checkout: true`, a dynamic value for that input, a known vulnerable commit or a semantic version below its release line's fixed version, or the workflow also uses an event that the guard does not cover. Moving major tags from v2 onward and branch-like refs are assumed to contain the guard. +Supported `actions/checkout` releases refuse fork pull request code by default for `pull_request_target` and pull-request-originated `workflow_run` events. Poutine suppresses this finding when that guard applies. Steps that execute shell scripts from `RUNNER_TEMP` (paths using `$RUNNER_TEMP` or `${RUNNER_TEMP}`) are also suppressed because those files are not part of the checked-out workspace. The finding remains when the checkout uses `allow-unsafe-pr-checkout: true`, a dynamic value for that input, a known vulnerable commit or a semantic version below its release line's fixed version, or the workflow also uses an event that the guard does not cover. Moving major tags from v2 onward and branch-like refs are assumed to contain the guard. Using workflows with `pull_request_target` has the added benefit (as opposed to `pull_request`) of allowing access to secrets even in forked repositories. There can be good reasons to do so if you need to use API Keys to talk to some external services or want to interact with the GitHub API with `write` permissions. However, this comes at the cost of paying extra attention to the tools you use in your workflow. diff --git a/opa/rego/rules/untrusted_checkout_exec.rego b/opa/rego/rules/untrusted_checkout_exec.rego index fbbb30f..fb31a11 100644 --- a/opa/rego/rules/untrusted_checkout_exec.rego +++ b/opa/rego/rules/untrusted_checkout_exec.rego @@ -99,6 +99,11 @@ build_commands[cmd] = { "yarn": {"yarn "}, }[cmd] +# Scripts under RUNNER_TEMP are provisioned by the runner, not the checked-out workspace. +_run_uses_runner_temp_path(run) if { + regex.match(`(?i)(?:\$\{\s*RUNNER_TEMP\s*\}|\$RUNNER_TEMP)/`, run) +} + results contains poutine.finding(rule, pkg_purl, object.union( { "path": workflow_path, @@ -116,6 +121,7 @@ results contains poutine.finding(rule, pkg_purl, object.union( sprintf("([^a-z]|^)(%v)", [concat("|", build_commands[cmd])]), step.run, ) + not _run_uses_runner_temp_path(step.run) } results contains poutine.finding(rule, pkg_purl, { diff --git a/scanner/runner_temp_test.go b/scanner/runner_temp_test.go new file mode 100644 index 0000000..e8fc1eb --- /dev/null +++ b/scanner/runner_temp_test.go @@ -0,0 +1,41 @@ +package scanner + +import ( + "context" + "testing" + + "github.com/boostsecurityio/poutine/models" + "github.com/boostsecurityio/poutine/opa" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRunnerTempScriptNotFlaggedAsUntrustedCheckoutExec(t *testing.T) { + o, err := opa.NewOpa(context.TODO(), &models.Config{ + Include: []models.ConfigInclude{}, + }) + require.NoError(t, err) + + i := NewInventory(o, nil, "", "") + pkg := &models.PackageInsights{ + Purl: "pkg:github/org/owner", + SourceGitRepo: "org/owner", + SourceGitRef: "main", + } + require.NoError(t, pkg.NormalizePurl()) + + scannedPackage, err := i.ScanPackage(context.Background(), *pkg, "testdata") + require.NoError(t, err) + + for _, finding := range scannedPackage.FindingsResults.Findings { + if finding.RuleId != "untrusted_checkout_exec" { + continue + } + assert.NotEqual( + t, + ".github/workflows/runner_temp_script.yml", + finding.Meta.Path, + "bash scripts under RUNNER_TEMP should not be treated as checked-out workspace code", + ) + } +} diff --git a/scanner/testdata/.github/workflows/runner_temp_script.yml b/scanner/testdata/.github/workflows/runner_temp_script.yml new file mode 100644 index 0000000..16fa6c3 --- /dev/null +++ b/scanner/testdata/.github/workflows/runner_temp_script.yml @@ -0,0 +1,19 @@ +name: runner-temp-script +on: + workflow_call: + inputs: + ref: + type: string + required: true +permissions: + contents: read +jobs: + run: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + sparse-checkout: .github + persist-credentials: false + - run: bash "${RUNNER_TEMP}/tooling/setup.sh" From acf59bb7454111e1485f968db6222be7eabb2aa0 Mon Sep 17 00:00:00 2001 From: Tyagiquamar Date: Tue, 13 Oct 2026 19:32:20 +0530 Subject: [PATCH 2/2] fix(scanner): expect the runner-temp workflow in the inventory fixture list The regression fixture lives under testdata, so the workflow inventory test has to include its path. Signed-off-by: Tyagiquamar --- scanner/inventory_scanner_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/scanner/inventory_scanner_test.go b/scanner/inventory_scanner_test.go index f63c3e1..c6d28bb 100644 --- a/scanner/inventory_scanner_test.go +++ b/scanner/inventory_scanner_test.go @@ -36,6 +36,7 @@ func TestGithubWorkflows(t *testing.T) { ".github/workflows/anchors_multiple.yml", ".github/workflows/anchors_with_vulnerability.yml", ".github/workflows/test_new_fields.yml", + ".github/workflows/runner_temp_script.yml", }) }