From 02dc7679aec7f7e3edd727051fe47552feb4197c Mon Sep 17 00:00:00 2001 From: 1sgtpepper Date: Wed, 22 Jul 2026 00:15:17 +0800 Subject: [PATCH 1/3] Fix untrusted checkout path normalization --- .../security/UntrustedCheckoutQuery.qll | 4 +- .../2026-07-21-untrusted-checkout-paths.md | 4 ++ .../workflows/untrusted_checkout_paths.yml | 42 +++++++++++++++++++ .../workflow_run_untrusted_checkout_path.yml | 16 +++++++ .../UntrustedCheckoutCritical.expected | 10 +++++ 5 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 actions/ql/src/change-notes/2026-07-21-untrusted-checkout-paths.md create mode 100644 actions/ql/test/query-tests/Security/CWE-829/.github/workflows/untrusted_checkout_paths.yml create mode 100644 actions/ql/test/query-tests/Security/CWE-829/.github/workflows/workflow_run_untrusted_checkout_path.yml diff --git a/actions/ql/lib/codeql/actions/security/UntrustedCheckoutQuery.qll b/actions/ql/lib/codeql/actions/security/UntrustedCheckoutQuery.qll index 9668fce2ae00..eb814294e6b9 100644 --- a/actions/ql/lib/codeql/actions/security/UntrustedCheckoutQuery.qll +++ b/actions/ql/lib/codeql/actions/security/UntrustedCheckoutQuery.qll @@ -259,7 +259,7 @@ class ActionsMutableRefCheckout extends MutableRefCheckoutStep instanceof UsesSt override string getPath() { if exists(this.(UsesStep).getArgument("path")) - then result = this.(UsesStep).getArgument("path") + then result = normalizePath(this.(UsesStep).getArgument("path")) else result = "GITHUB_WORKSPACE/" } } @@ -294,7 +294,7 @@ class ActionsSHACheckout extends SHACheckoutStep instanceof UsesStep { override string getPath() { if exists(this.(UsesStep).getArgument("path")) - then result = this.(UsesStep).getArgument("path") + then result = normalizePath(this.(UsesStep).getArgument("path")) else result = "GITHUB_WORKSPACE/" } } diff --git a/actions/ql/src/change-notes/2026-07-21-untrusted-checkout-paths.md b/actions/ql/src/change-notes/2026-07-21-untrusted-checkout-paths.md new file mode 100644 index 000000000000..95685ca43e4f --- /dev/null +++ b/actions/ql/src/change-notes/2026-07-21-untrusted-checkout-paths.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `actions/untrusted-checkout/critical` query now recognizes local scripts and actions run from an explicit relative `actions/checkout` path. diff --git a/actions/ql/test/query-tests/Security/CWE-829/.github/workflows/untrusted_checkout_paths.yml b/actions/ql/test/query-tests/Security/CWE-829/.github/workflows/untrusted_checkout_paths.yml new file mode 100644 index 000000000000..d8ac3e5133e9 --- /dev/null +++ b/actions/ql/test/query-tests/Security/CWE-829/.github/workflows/untrusted_checkout_paths.yml @@ -0,0 +1,42 @@ +name: Untrusted checkout paths + +on: + issue_comment: + types: [created] + +jobs: + bare-path: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + ref: ${{ github.head_ref }} + path: checkout + - run: ./checkout/build.sh + + dot-relative-path: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + ref: ${{ github.head_ref }} + path: ./checkout-dot + - run: bash ./checkout-dot/build.sh + + trailing-slash-path: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + ref: ${{ github.head_ref }} + path: checkout-action/ + - uses: ./checkout-action/.github/actions/build + + nested-quoted-path: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + ref: ${{ github.head_ref }} + path: "a/b" + - run: bash ./a/b/build.sh diff --git a/actions/ql/test/query-tests/Security/CWE-829/.github/workflows/workflow_run_untrusted_checkout_path.yml b/actions/ql/test/query-tests/Security/CWE-829/.github/workflows/workflow_run_untrusted_checkout_path.yml new file mode 100644 index 000000000000..d9396afad90e --- /dev/null +++ b/actions/ql/test/query-tests/Security/CWE-829/.github/workflows/workflow_run_untrusted_checkout_path.yml @@ -0,0 +1,16 @@ +name: Workflow run untrusted checkout path + +on: + workflow_run: + workflows: [Test] + types: [completed] + +jobs: + nested-path: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + ref: ${{ github.event.workflow_run.head_sha }} + path: nested/checkout + - run: ./nested/checkout/build.sh diff --git a/actions/ql/test/query-tests/Security/CWE-829/UntrustedCheckoutCritical.expected b/actions/ql/test/query-tests/Security/CWE-829/UntrustedCheckoutCritical.expected index b6c349bd64fe..2d5484ef5e0e 100644 --- a/actions/ql/test/query-tests/Security/CWE-829/UntrustedCheckoutCritical.expected +++ b/actions/ql/test/query-tests/Security/CWE-829/UntrustedCheckoutCritical.expected @@ -338,6 +338,10 @@ edges | .github/workflows/untrusted_checkout_6.yml:17:9:21:6 | Uses Step | .github/workflows/untrusted_checkout_6.yml:21:9:23:23 | Run Step | | .github/workflows/untrusted_checkout_no_needs.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_no_needs.yml:16:9:22:2 | Run Step | | .github/workflows/untrusted_checkout_no_needs.yml:26:9:31:6 | Uses Step | .github/workflows/untrusted_checkout_no_needs.yml:31:9:31:23 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:11:9:15:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:15:9:17:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:20:9:24:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:24:9:26:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:29:9:33:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:33:9:35:2 | Uses Step | +| .github/workflows/untrusted_checkout_paths.yml:38:9:42:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:42:9:42:33 | Run Step | | .github/workflows/untrusted_checkout_permission_check_reusable2.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_permission_check_reusable2.yml:16:9:22:2 | Run Step | | .github/workflows/untrusted_checkout_permission_check_reusable.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_permission_check_reusable.yml:16:9:22:2 | Run Step | | .github/workflows/untrusted_checkout_permission_check_reusable_level2.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_permission_check_reusable_level2.yml:16:9:22:2 | Run Step | @@ -350,6 +354,7 @@ edges | .github/workflows/workflow_run_untrusted_checkout.yml:13:9:16:6 | Uses Step | .github/workflows/workflow_run_untrusted_checkout.yml:16:9:18:31 | Uses Step | | .github/workflows/workflow_run_untrusted_checkout_2.yml:13:9:16:6 | Uses Step | .github/workflows/workflow_run_untrusted_checkout_2.yml:16:9:18:31 | Uses Step | | .github/workflows/workflow_run_untrusted_checkout_3.yml:13:9:16:6 | Uses Step | .github/workflows/workflow_run_untrusted_checkout_3.yml:16:9:18:31 | Uses Step | +| .github/workflows/workflow_run_untrusted_checkout_path.yml:12:9:16:6 | Uses Step | .github/workflows/workflow_run_untrusted_checkout_path.yml:16:9:16:40 | Run Step | #select | .github/actions/dangerous-git-checkout/action.yml:6:7:11:4 | Uses Step | .github/actions/dangerous-git-checkout/action.yml:6:7:11:4 | Uses Step | .github/workflows/untrusted_checkout3.yml:13:9:13:23 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout3.yml:4:3:4:14 | workflow_run | workflow_run | | .github/workflows/auto_ci.yml:20:9:27:6 | Uses Step | .github/workflows/auto_ci.yml:20:9:27:6 | Uses Step | .github/workflows/auto_ci.yml:32:9:37:6 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/auto_ci.yml:6:3:6:21 | pull_request_target | pull_request_target | @@ -394,4 +399,9 @@ edges | .github/workflows/untrusted_checkout.yml:8:9:11:6 | Uses Step | .github/workflows/untrusted_checkout.yml:8:9:11:6 | Uses Step | .github/workflows/untrusted_checkout.yml:15:9:18:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout.yml:2:3:2:21 | pull_request_target | pull_request_target | | .github/workflows/untrusted_checkout.yml:23:9:26:6 | Uses Step | .github/workflows/untrusted_checkout.yml:23:9:26:6 | Uses Step | .github/workflows/untrusted_checkout.yml:30:9:32:23 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout.yml:2:3:2:21 | pull_request_target | pull_request_target | | .github/workflows/untrusted_checkout_no_needs.yml:26:9:31:6 | Uses Step | .github/workflows/untrusted_checkout_no_needs.yml:26:9:31:6 | Uses Step | .github/workflows/untrusted_checkout_no_needs.yml:31:9:31:23 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_no_needs.yml:2:3:2:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:11:9:15:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:11:9:15:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:15:9:17:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:15 | issue_comment | issue_comment | +| .github/workflows/untrusted_checkout_paths.yml:20:9:24:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:20:9:24:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:24:9:26:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:15 | issue_comment | issue_comment | +| .github/workflows/untrusted_checkout_paths.yml:29:9:33:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:29:9:33:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:33:9:35:2 | Uses Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:15 | issue_comment | issue_comment | +| .github/workflows/untrusted_checkout_paths.yml:38:9:42:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:38:9:42:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:42:9:42:33 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:15 | issue_comment | issue_comment | | .github/workflows/untrusted_checkout_permissions_check.yml:36:9:41:6 | Uses Step | .github/workflows/untrusted_checkout_permissions_check.yml:36:9:41:6 | Uses Step | .github/workflows/untrusted_checkout_permissions_check.yml:41:9:41:22 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_permissions_check.yml:2:3:2:21 | pull_request_target | pull_request_target | +| .github/workflows/workflow_run_untrusted_checkout_path.yml:12:9:16:6 | Uses Step | .github/workflows/workflow_run_untrusted_checkout_path.yml:12:9:16:6 | Uses Step | .github/workflows/workflow_run_untrusted_checkout_path.yml:16:9:16:40 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/workflow_run_untrusted_checkout_path.yml:4:3:4:14 | workflow_run | workflow_run | From d0263736477a4b8d1ac1ccc527122415dc9f13c5 Mon Sep 17 00:00:00 2001 From: 1sgtpepper Date: Wed, 22 Jul 2026 00:34:46 +0800 Subject: [PATCH 2/3] Clarify nested checkout path coverage --- .../CWE-829/.github/workflows/untrusted_checkout_paths.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/ql/test/query-tests/Security/CWE-829/.github/workflows/untrusted_checkout_paths.yml b/actions/ql/test/query-tests/Security/CWE-829/.github/workflows/untrusted_checkout_paths.yml index d8ac3e5133e9..afef8cd538b1 100644 --- a/actions/ql/test/query-tests/Security/CWE-829/.github/workflows/untrusted_checkout_paths.yml +++ b/actions/ql/test/query-tests/Security/CWE-829/.github/workflows/untrusted_checkout_paths.yml @@ -32,11 +32,11 @@ jobs: path: checkout-action/ - uses: ./checkout-action/.github/actions/build - nested-quoted-path: + nested-path: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.2.2 with: ref: ${{ github.head_ref }} - path: "a/b" + path: a/b - run: bash ./a/b/build.sh From 9e9c94535b5ef8f77f34829a230ac9f6f2b43d21 Mon Sep 17 00:00:00 2001 From: 1sgtpepper Date: Wed, 22 Jul 2026 20:51:09 +0800 Subject: [PATCH 3/3] Harden checkout path containment --- .../2026-07-22-untrusted-checkout-paths.md | 4 + .../actions/security/CachePoisoningQuery.qll | 55 +++++ .../actions/security/PoisonableSteps.qll | 70 +++++- .../security/UntrustedCheckoutQuery.qll | 99 ++++++++- .../CWE-349/CachePoisoningViaDirectCache.ql | 9 +- .../CWE-829/UntrustedCheckoutCritical.ql | 6 +- .../2026-07-21-untrusted-checkout-paths.md | 2 +- .../.github/workflows/direct_cache7.yml | 72 ++++++ .../CachePoisoningViaDirectCache.expected | 7 + .../CachePoisoningViaPoisonableStep.expected | 5 + .../workflows/untrusted_checkout_paths.yml | 208 +++++++++++++++++- .../UntrustedCheckoutCritical.expected | 47 +++- .../CWE-829/UntrustedCheckoutHigh.expected | 1 + 13 files changed, 554 insertions(+), 31 deletions(-) create mode 100644 actions/ql/lib/change-notes/2026-07-22-untrusted-checkout-paths.md create mode 100644 actions/ql/test/query-tests/Security/CWE-349/.github/workflows/direct_cache7.yml diff --git a/actions/ql/lib/change-notes/2026-07-22-untrusted-checkout-paths.md b/actions/ql/lib/change-notes/2026-07-22-untrusted-checkout-paths.md new file mode 100644 index 000000000000..a483881e7b66 --- /dev/null +++ b/actions/ql/lib/change-notes/2026-07-22-untrusted-checkout-paths.md @@ -0,0 +1,4 @@ +--- +category: feature +--- +* `PRHeadCheckoutStep.getPath()` now returns canonical workspace paths when they are statically resolvable, while unresolved paths remain symbolic. `PRHeadCheckoutStep.containsPath` and canonical local-execution and cache-path accessors provide component-aware containment without changing the existing local-execution or cache-path contracts. diff --git a/actions/ql/lib/codeql/actions/security/CachePoisoningQuery.qll b/actions/ql/lib/codeql/actions/security/CachePoisoningQuery.qll index e5c5a3655101..078b2abc48ef 100644 --- a/actions/ql/lib/codeql/actions/security/CachePoisoningQuery.qll +++ b/actions/ql/lib/codeql/actions/security/CachePoisoningQuery.qll @@ -1,4 +1,46 @@ import actions +private import codeql.util.FilePath + +private class CachePathInput extends NormalizableFilepath { + CachePathInput() { + exists(CacheActionUsesStep step | + this = trimQuotes(step.(UsesStep).getArgument("path").splitAt("\n")) + ) + or + exists(CacheActionSaveUsesStep step | + this = trimQuotes(step.(UsesStep).getArgument("path").splitAt("\n")) + ) + or + this = "vendor/bundle" + } +} + +bindingset[path] +private string normalizeCachePath(string path) { + if path.regexpMatch(".*\\$.*") + then result = trimQuotes(path) + else + exists(CachePathInput cachePath, string normalized | + cachePath = trimQuotes(path) and + normalized = cachePath.getNormalizedPath() and + if cachePath = "" + then result = cachePath + else + if normalized = "GITHUB_WORKSPACE" or normalized = "." + then result = "GITHUB_WORKSPACE/" + else + if normalized.matches("GITHUB_WORKSPACE/%") + then result = normalized + else + // A leading `..` cannot be resolved without the concrete workspace path. + if normalized = ".." or normalized.matches("../%") + then result = cachePath + else + if cachePath.regexpMatch("^[^$/~].*") + then result = "GITHUB_WORKSPACE/" + normalized + else result = cachePath + ) +} string defaultBranchTriggerEvent() { result = @@ -44,6 +86,9 @@ predicate runsOnDefaultBranch(Event e) { abstract class CacheWritingStep extends Step { abstract string getPath(); + + /** Gets a canonical workspace path when statically resolvable, or a symbolic path otherwise. */ + string getCanonicalPath() { result = this.getPath() } } class CacheActionUsesStep extends CacheWritingStep, UsesStep { @@ -52,6 +97,10 @@ class CacheActionUsesStep extends CacheWritingStep, UsesStep { override string getPath() { result = normalizePath(this.(UsesStep).getArgument("path").splitAt("\n")) } + + override string getCanonicalPath() { + result = normalizeCachePath(this.(UsesStep).getArgument("path").splitAt("\n")) + } } class CacheActionSaveUsesStep extends CacheWritingStep, UsesStep { @@ -60,6 +109,10 @@ class CacheActionSaveUsesStep extends CacheWritingStep, UsesStep { override string getPath() { result = normalizePath(this.(UsesStep).getArgument("path").splitAt("\n")) } + + override string getCanonicalPath() { + result = normalizeCachePath(this.(UsesStep).getArgument("path").splitAt("\n")) + } } class SetupRubyUsesStep extends CacheWritingStep, UsesStep { @@ -69,4 +122,6 @@ class SetupRubyUsesStep extends CacheWritingStep, UsesStep { } override string getPath() { result = normalizePath("vendor/bundle") } + + override string getCanonicalPath() { result = normalizeCachePath("vendor/bundle") } } diff --git a/actions/ql/lib/codeql/actions/security/PoisonableSteps.qll b/actions/ql/lib/codeql/actions/security/PoisonableSteps.qll index 1f3bc66bd776..7338b5fed091 100644 --- a/actions/ql/lib/codeql/actions/security/PoisonableSteps.qll +++ b/actions/ql/lib/codeql/actions/security/PoisonableSteps.qll @@ -1,4 +1,52 @@ import actions +private import codeql.util.FilePath + +private predicate localScriptPath(Run step, string command, string path) { + exists(string cmd, string regexp, int pathGroup | cmd = step.getScript().getACommand() | + poisonableLocalScriptsDataModel(regexp, pathGroup) and + command = cmd and + path = command.regexpCapture(regexp, pathGroup) + ) +} + +private predicate localScriptPath(Run step, string path) { + exists(string command | localScriptPath(step, command, path)) +} + +private class PoisonableLocalPath extends NormalizableFilepath { + PoisonableLocalPath() { + exists(Run step, string path | + localScriptPath(step, path) and + this = trimQuotes(path.splitAt(" ")) + ) + or + exists(LocalActionUsesStep step | this = trimQuotes(step.getCallee())) + } +} + +bindingset[path] +private string normalizePoisonableLocalPath(string path) { + if path.regexpMatch(".*\\$.*") + then result = trimQuotes(path) + else + exists(PoisonableLocalPath localPath, string normalized | + localPath = trimQuotes(path) and + normalized = localPath.getNormalizedPath() and + if normalized = "GITHUB_WORKSPACE" or normalized = "." + then result = "GITHUB_WORKSPACE/" + else + if normalized.matches("GITHUB_WORKSPACE/%") + then result = normalized + else + // A leading `..` cannot be resolved without the concrete workspace path. + if normalized = ".." or normalized.matches("../%") + then result = localPath + else + if localPath.regexpMatch("^[^$/~].*") + then result = "GITHUB_WORKSPACE/" + normalized + else result = localPath + ) +} abstract class PoisonableStep extends Step { } @@ -39,18 +87,28 @@ class SetupNodeUsesStep extends PoisonableStep, UsesStep { class LocalScriptExecutionRunStep extends PoisonableStep, Run { string path; - LocalScriptExecutionRunStep() { - exists(string cmd, string regexp, int path_group | cmd = this.getScript().getACommand() | - poisonableLocalScriptsDataModel(regexp, path_group) and - path = cmd.regexpCapture(regexp, path_group) - ) - } + LocalScriptExecutionRunStep() { localScriptPath(this, path) } string getPath() { result = normalizePath(path.splitAt(" ")) } + + /** Gets a canonical workspace path when statically resolvable, or a symbolic path otherwise. */ + string getCanonicalPath() { + // The script matcher can capture only the static prefix before an embedded expression. + if + exists(string command | + localScriptPath(this, command, path) and + (command.indexOf(path + "$") >= 0 or command.indexOf(path + "/$") >= 0) + ) + then result = trimQuotes(path.splitAt(" ")) + else result = normalizePoisonableLocalPath(path.splitAt(" ")) + } } class LocalActionUsesStep extends PoisonableStep, UsesStep { LocalActionUsesStep() { this.getCallee().matches("./%") } string getPath() { result = normalizePath(this.getCallee()) } + + /** Gets a canonical workspace path when statically resolvable, or a symbolic path otherwise. */ + string getCanonicalPath() { result = normalizePoisonableLocalPath(this.getCallee()) } } diff --git a/actions/ql/lib/codeql/actions/security/UntrustedCheckoutQuery.qll b/actions/ql/lib/codeql/actions/security/UntrustedCheckoutQuery.qll index eb814294e6b9..da8efc45590e 100644 --- a/actions/ql/lib/codeql/actions/security/UntrustedCheckoutQuery.qll +++ b/actions/ql/lib/codeql/actions/security/UntrustedCheckoutQuery.qll @@ -1,8 +1,78 @@ import actions +private import codeql.util.FilePath private import codeql.actions.DataFlow private import codeql.actions.dataflow.FlowSources private import codeql.actions.TaintTracking +/** An `actions/checkout` path input. */ +private class ActionsCheckoutPathInput extends NormalizableFilepath { + ActionsCheckoutPathInput() { + exists(UsesStep checkout | + checkout.getCallee() = "actions/checkout" and + this = trimQuotes(checkout.getArgument("path")) + ) + } +} + +/** A working directory used by a `git` or `gh` checkout step. */ +private class RunCheckoutPath extends NormalizableFilepath { + RunCheckoutPath() { + exists(PRHeadCheckoutStep checkout | + checkout instanceof Run and + this = trimQuotes(checkout.(Run).getWorkingDirectory()) + ) + } +} + +bindingset[path] +private string normalizeActionsCheckoutPath(string path) { + if path.regexpMatch(".*\\$\\{\\{.*") + then result = trimQuotes(path) + else + exists(ActionsCheckoutPathInput checkoutPath, string normalized | + checkoutPath = trimQuotes(path) and + normalized = checkoutPath.getNormalizedPath() and + // Resolving these forms requires the concrete workspace path, which the model does not know. + if normalized.matches("/%") or normalized = ".." or normalized.matches("../%") + then result = checkoutPath + else + if normalized = "." + then result = "GITHUB_WORKSPACE/" + else result = "GITHUB_WORKSPACE/" + normalized + ) +} + +bindingset[path] +private string normalizeRunCheckoutPath(string path) { + if path.regexpMatch(".*\\$\\{\\{.*") + then result = trimQuotes(path) + else + exists(RunCheckoutPath checkoutPath, string normalized | + checkoutPath = trimQuotes(path) and + normalized = checkoutPath.getNormalizedPath() and + if normalized = "GITHUB_WORKSPACE" or normalized = "." + then result = "GITHUB_WORKSPACE/" + else + if normalized.matches("GITHUB_WORKSPACE/%") + then result = normalized + else + if normalized.matches("/%") or normalized = ".." or normalized.matches("../%") + then result = checkoutPath + else result = "GITHUB_WORKSPACE/" + normalized + ) +} + +bindingset[path] +private predicate isCanonicalWorkspacePath(string path) { + (path = "GITHUB_WORKSPACE" or path.matches("GITHUB_WORKSPACE/%")) and + not path.regexpMatch(".*\\$.*") and + not path.matches("%//%") and + not path.matches("%/./%") and + not path.matches("%/../%") and + not path.matches("%/.") and + not path.matches("%/..") +} + string checkoutTriggers() { result = ["pull_request_target", "workflow_run", "workflow_call", "issue_comment"] } @@ -218,7 +288,24 @@ class SimplePRHeadCheckoutStep extends Step { /** Checkout of a Pull Request HEAD */ abstract class PRHeadCheckoutStep extends Step { + /** Gets the modeled root directory of this checkout. */ abstract string getPath(); + + /** Holds if canonical workspace `path` is the checkout root or a slash-separated descendant. */ + bindingset[path] + predicate containsPath(string path) { + exists(string root, string candidate | + root = this.getPath().regexpReplaceAll("/+$", "") and + candidate = path.regexpReplaceAll("/+$", "") and + isCanonicalWorkspacePath(root) and + isCanonicalWorkspacePath(candidate) and + ( + candidate = root + or + candidate.indexOf(root + "/") = 0 + ) + ) + } } /** Checkout of a Pull Request HEAD ref */ @@ -259,7 +346,7 @@ class ActionsMutableRefCheckout extends MutableRefCheckoutStep instanceof UsesSt override string getPath() { if exists(this.(UsesStep).getArgument("path")) - then result = normalizePath(this.(UsesStep).getArgument("path")) + then result = normalizeActionsCheckoutPath(this.(UsesStep).getArgument("path")) else result = "GITHUB_WORKSPACE/" } } @@ -294,7 +381,7 @@ class ActionsSHACheckout extends SHACheckoutStep instanceof UsesStep { override string getPath() { if exists(this.(UsesStep).getArgument("path")) - then result = normalizePath(this.(UsesStep).getArgument("path")) + then result = normalizeActionsCheckoutPath(this.(UsesStep).getArgument("path")) else result = "GITHUB_WORKSPACE/" } } @@ -319,7 +406,7 @@ class GitMutableRefCheckout extends MutableRefCheckoutStep instanceof Run { ) } - override string getPath() { result = this.(Run).getWorkingDirectory() } + override string getPath() { result = normalizeRunCheckoutPath(this.(Run).getWorkingDirectory()) } } /** Checkout of a Pull Request HEAD ref using git within a Run step */ @@ -339,7 +426,7 @@ class GitSHACheckout extends SHACheckoutStep instanceof Run { ) } - override string getPath() { result = this.(Run).getWorkingDirectory() } + override string getPath() { result = normalizeRunCheckoutPath(this.(Run).getWorkingDirectory()) } } /** Checkout of a Pull Request HEAD ref using gh within a Run step */ @@ -361,7 +448,7 @@ class GhMutableRefCheckout extends MutableRefCheckoutStep instanceof Run { ) } - override string getPath() { result = this.(Run).getWorkingDirectory() } + override string getPath() { result = normalizeRunCheckoutPath(this.(Run).getWorkingDirectory()) } } /** Checkout of a Pull Request HEAD ref using gh within a Run step */ @@ -380,5 +467,5 @@ class GhSHACheckout extends SHACheckoutStep instanceof Run { ) } - override string getPath() { result = this.(Run).getWorkingDirectory() } + override string getPath() { result = normalizeRunCheckoutPath(this.(Run).getWorkingDirectory()) } } diff --git a/actions/ql/src/Security/CWE-349/CachePoisoningViaDirectCache.ql b/actions/ql/src/Security/CWE-349/CachePoisoningViaDirectCache.ql index 85a0f53df1dc..c90775842688 100644 --- a/actions/ql/src/Security/CWE-349/CachePoisoningViaDirectCache.ql +++ b/actions/ql/src/Security/CWE-349/CachePoisoningViaDirectCache.ql @@ -63,7 +63,14 @@ where step.(CacheWritingStep).getPath() = "?" or // the cache writing step reads from a path the attacker can control - not path = "?" and isSubpath(step.(CacheWritingStep).getPath(), path) + not path = "?" and + ( + source instanceof PRHeadCheckoutStep and + source.(PRHeadCheckoutStep).containsPath(step.(CacheWritingStep).getCanonicalPath()) + or + source instanceof UntrustedArtifactDownloadStep and + isSubpath(step.(CacheWritingStep).getPath(), path) + ) ) and not step instanceof PoisonableStep select step, source, step, diff --git a/actions/ql/src/Security/CWE-829/UntrustedCheckoutCritical.ql b/actions/ql/src/Security/CWE-829/UntrustedCheckoutCritical.ql index e0af03ca3d02..ca040fa75d94 100644 --- a/actions/ql/src/Security/CWE-829/UntrustedCheckoutCritical.ql +++ b/actions/ql/src/Security/CWE-829/UntrustedCheckoutCritical.ql @@ -28,8 +28,8 @@ where poisonable instanceof Run and ( // Check if the poisonable step is a local script execution step - // and the path of the command or script matches the path of the downloaded artifact - isSubpath(poisonable.(LocalScriptExecutionRunStep).getPath(), checkout.getPath()) + // and the command or script is within the checkout directory + checkout.containsPath(poisonable.(LocalScriptExecutionRunStep).getCanonicalPath()) or // Checking the path for non local script execution steps is very difficult not poisonable instanceof LocalScriptExecutionRunStep @@ -42,7 +42,7 @@ where not poisonable instanceof LocalActionUsesStep and checkout.getPath() = "GITHUB_WORKSPACE/" or - isSubpath(poisonable.(LocalActionUsesStep).getPath(), checkout.getPath()) + checkout.containsPath(poisonable.(LocalActionUsesStep).getCanonicalPath()) ) ) and // the checkout occurs in a privileged context diff --git a/actions/ql/src/change-notes/2026-07-21-untrusted-checkout-paths.md b/actions/ql/src/change-notes/2026-07-21-untrusted-checkout-paths.md index 95685ca43e4f..3ac46ab41f15 100644 --- a/actions/ql/src/change-notes/2026-07-21-untrusted-checkout-paths.md +++ b/actions/ql/src/change-notes/2026-07-21-untrusted-checkout-paths.md @@ -1,4 +1,4 @@ --- category: minorAnalysis --- -* The `actions/untrusted-checkout/critical` query now recognizes local scripts and actions run from an explicit relative `actions/checkout` path. +* The `actions/untrusted-checkout/critical` and `actions/cache-poisoning/direct-cache` queries now canonicalize statically resolvable checkout, execution, and cache paths before testing component-aware containment. This adds results for explicit checkout directories and equivalent dot or redundant separator spellings without matching sibling or traversed-outside directories. diff --git a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/direct_cache7.yml b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/direct_cache7.yml new file mode 100644 index 000000000000..288a9b480a5e --- /dev/null +++ b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/direct_cache7.yml @@ -0,0 +1,72 @@ +name: Explicit checkout cache paths + +on: pull_request_target + +permissions: + actions: write + +jobs: + checkout-cache: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: checkout + - uses: actions/cache@v4 + with: + path: ./checkout/ + key: checkout-cache + + sibling-cache: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: checkout + - uses: actions/cache@v4 + with: + path: ./checkout-other/cache + key: sibling-cache + + canonical-cache-path: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: a/b + - uses: actions/cache@v4 + with: + path: ./a//intermediate/../b/./cache + key: canonical-cache + + traversing-cache-path: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: checkout + - uses: actions/cache@v4 + with: + path: ./checkout/../checkout-other/cache + key: traversing-cache + + dynamic-cache-path: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: checkout + - uses: actions/cache@v4 + with: + path: ./checkout/${{ github.event.pull_request.title }}/cache + key: dynamic-cache diff --git a/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaDirectCache.expected b/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaDirectCache.expected index 4cc8536b5943..e5538be5c199 100644 --- a/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaDirectCache.expected +++ b/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaDirectCache.expected @@ -14,6 +14,11 @@ edges | .github/workflows/direct_cache5.yml:17:9:21:6 | Uses Step | .github/workflows/direct_cache5.yml:21:9:22:21 | Run Step | | .github/workflows/direct_cache6.yml:13:9:16:6 | Uses Step | .github/workflows/direct_cache6.yml:16:9:20:6 | Uses Step | | .github/workflows/direct_cache6.yml:16:9:20:6 | Uses Step | .github/workflows/direct_cache6.yml:20:9:26:46 | Uses Step: cache-pip | +| .github/workflows/direct_cache7.yml:12:9:17:6 | Uses Step | .github/workflows/direct_cache7.yml:17:9:22:2 | Uses Step | +| .github/workflows/direct_cache7.yml:25:9:30:6 | Uses Step | .github/workflows/direct_cache7.yml:30:9:35:2 | Uses Step | +| .github/workflows/direct_cache7.yml:38:9:43:6 | Uses Step | .github/workflows/direct_cache7.yml:43:9:48:2 | Uses Step | +| .github/workflows/direct_cache7.yml:51:9:56:6 | Uses Step | .github/workflows/direct_cache7.yml:56:9:61:2 | Uses Step | +| .github/workflows/direct_cache7.yml:64:9:69:6 | Uses Step | .github/workflows/direct_cache7.yml:69:9:72:29 | Uses Step | | .github/workflows/neg_direct_cache1.yml:14:9:17:6 | Uses Step | .github/workflows/neg_direct_cache1.yml:17:9:21:6 | Uses Step | | .github/workflows/neg_direct_cache1.yml:17:9:21:6 | Uses Step | .github/workflows/neg_direct_cache1.yml:21:9:22:21 | Run Step | | .github/workflows/neg_direct_cache2.yml:14:9:17:6 | Uses Step | .github/workflows/neg_direct_cache2.yml:17:9:21:6 | Uses Step | @@ -50,3 +55,5 @@ edges | .github/workflows/direct_cache4.yml:17:9:21:6 | Uses Step | .github/workflows/direct_cache4.yml:14:9:17:6 | Uses Step | .github/workflows/direct_cache4.yml:17:9:21:6 | Uses Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code. ($@). | .github/workflows/direct_cache4.yml:4:3:4:21 | pull_request_target | pull_request_target | | .github/workflows/direct_cache5.yml:17:9:21:6 | Uses Step | .github/workflows/direct_cache5.yml:14:9:17:6 | Uses Step | .github/workflows/direct_cache5.yml:17:9:21:6 | Uses Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code. ($@). | .github/workflows/direct_cache5.yml:4:3:4:21 | pull_request_target | pull_request_target | | .github/workflows/direct_cache6.yml:20:9:26:46 | Uses Step: cache-pip | .github/workflows/direct_cache6.yml:13:9:16:6 | Uses Step | .github/workflows/direct_cache6.yml:20:9:26:46 | Uses Step: cache-pip | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code. ($@). | .github/workflows/direct_cache6.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/direct_cache7.yml:17:9:22:2 | Uses Step | .github/workflows/direct_cache7.yml:12:9:17:6 | Uses Step | .github/workflows/direct_cache7.yml:17:9:22:2 | Uses Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code. ($@). | .github/workflows/direct_cache7.yml:3:5:3:23 | pull_request_target | pull_request_target | +| .github/workflows/direct_cache7.yml:43:9:48:2 | Uses Step | .github/workflows/direct_cache7.yml:38:9:43:6 | Uses Step | .github/workflows/direct_cache7.yml:43:9:48:2 | Uses Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code. ($@). | .github/workflows/direct_cache7.yml:3:5:3:23 | pull_request_target | pull_request_target | diff --git a/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaPoisonableStep.expected b/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaPoisonableStep.expected index 6b1a3e873134..d8c6c38bf7d0 100644 --- a/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaPoisonableStep.expected +++ b/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaPoisonableStep.expected @@ -14,6 +14,11 @@ edges | .github/workflows/direct_cache5.yml:17:9:21:6 | Uses Step | .github/workflows/direct_cache5.yml:21:9:22:21 | Run Step | | .github/workflows/direct_cache6.yml:13:9:16:6 | Uses Step | .github/workflows/direct_cache6.yml:16:9:20:6 | Uses Step | | .github/workflows/direct_cache6.yml:16:9:20:6 | Uses Step | .github/workflows/direct_cache6.yml:20:9:26:46 | Uses Step: cache-pip | +| .github/workflows/direct_cache7.yml:12:9:17:6 | Uses Step | .github/workflows/direct_cache7.yml:17:9:22:2 | Uses Step | +| .github/workflows/direct_cache7.yml:25:9:30:6 | Uses Step | .github/workflows/direct_cache7.yml:30:9:35:2 | Uses Step | +| .github/workflows/direct_cache7.yml:38:9:43:6 | Uses Step | .github/workflows/direct_cache7.yml:43:9:48:2 | Uses Step | +| .github/workflows/direct_cache7.yml:51:9:56:6 | Uses Step | .github/workflows/direct_cache7.yml:56:9:61:2 | Uses Step | +| .github/workflows/direct_cache7.yml:64:9:69:6 | Uses Step | .github/workflows/direct_cache7.yml:69:9:72:29 | Uses Step | | .github/workflows/neg_direct_cache1.yml:14:9:17:6 | Uses Step | .github/workflows/neg_direct_cache1.yml:17:9:21:6 | Uses Step | | .github/workflows/neg_direct_cache1.yml:17:9:21:6 | Uses Step | .github/workflows/neg_direct_cache1.yml:21:9:22:21 | Run Step | | .github/workflows/neg_direct_cache2.yml:14:9:17:6 | Uses Step | .github/workflows/neg_direct_cache2.yml:17:9:21:6 | Uses Step | diff --git a/actions/ql/test/query-tests/Security/CWE-829/.github/workflows/untrusted_checkout_paths.yml b/actions/ql/test/query-tests/Security/CWE-829/.github/workflows/untrusted_checkout_paths.yml index afef8cd538b1..30455226286c 100644 --- a/actions/ql/test/query-tests/Security/CWE-829/.github/workflows/untrusted_checkout_paths.yml +++ b/actions/ql/test/query-tests/Security/CWE-829/.github/workflows/untrusted_checkout_paths.yml @@ -1,8 +1,7 @@ name: Untrusted checkout paths on: - issue_comment: - types: [created] + pull_request_target: jobs: bare-path: @@ -10,7 +9,8 @@ jobs: steps: - uses: actions/checkout@v4.2.2 with: - ref: ${{ github.head_ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} path: checkout - run: ./checkout/build.sh @@ -19,7 +19,8 @@ jobs: steps: - uses: actions/checkout@v4.2.2 with: - ref: ${{ github.head_ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} path: ./checkout-dot - run: bash ./checkout-dot/build.sh @@ -28,7 +29,8 @@ jobs: steps: - uses: actions/checkout@v4.2.2 with: - ref: ${{ github.head_ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} path: checkout-action/ - uses: ./checkout-action/.github/actions/build @@ -37,6 +39,200 @@ jobs: steps: - uses: actions/checkout@v4.2.2 with: - ref: ${{ github.head_ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} path: a/b - run: bash ./a/b/build.sh + + workspace-dot: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: . + - run: ./build.sh + + workspace-empty: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: + - run: ./build.sh + + workspace-dot-slash: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: ./ + - run: ./build.sh + + parent-segment: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: a/../checkout-parent + - run: ./checkout-parent/build.sh + + internal-dot-segment: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: a/./b + - run: ./a/b/build.sh + + repeated-separators: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: a//b///c/ + - run: ./a/b/c/build.sh + + sibling-directory: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: checkout + - run: ./checkout-other/build.sh + + nested-sibling-directory: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: a/b + - run: ./a/b-other/build.sh + + unrelated-directory: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: checkout + - run: ./unrelated/build.sh + + dynamic-path: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: ${{ github.event.pull_request.head.ref }} + - run: ./build.sh + + candidate-repeated-separators: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: a/b + - run: ./a//b/build.sh + + candidate-dot-segment: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: a/b + - run: ./a/./b/build.sh + + candidate-parent-segment-inside: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: a/b + - run: ./a/intermediate/../b/build.sh + + local-action-dot-segment: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: a/b + - uses: ./a/./b/.github/actions/build + + candidate-parent-segment-outside: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: checkout + - run: ./checkout/../checkout-other/build.sh + + dynamic-candidate-path: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: checkout + - run: ./checkout/${{ github.event.pull_request.title }}/build.sh + + workspace-reentry-path: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: ../codeql/reentered + + git-working-directory: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + path: checkout + - run: | + git fetch origin ${{ github.event.pull_request.head.sha }} + git checkout FETCH_HEAD + working-directory: checkout + - run: ./checkout/build.sh + + git-working-directory-sibling: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + with: + path: checkout + - run: | + git fetch origin ${{ github.event.pull_request.head.sha }} + git checkout FETCH_HEAD + working-directory: checkout + - run: ./checkout-other/build.sh diff --git a/actions/ql/test/query-tests/Security/CWE-829/UntrustedCheckoutCritical.expected b/actions/ql/test/query-tests/Security/CWE-829/UntrustedCheckoutCritical.expected index 2d5484ef5e0e..fca12b074f4d 100644 --- a/actions/ql/test/query-tests/Security/CWE-829/UntrustedCheckoutCritical.expected +++ b/actions/ql/test/query-tests/Security/CWE-829/UntrustedCheckoutCritical.expected @@ -338,10 +338,30 @@ edges | .github/workflows/untrusted_checkout_6.yml:17:9:21:6 | Uses Step | .github/workflows/untrusted_checkout_6.yml:21:9:23:23 | Run Step | | .github/workflows/untrusted_checkout_no_needs.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_no_needs.yml:16:9:22:2 | Run Step | | .github/workflows/untrusted_checkout_no_needs.yml:26:9:31:6 | Uses Step | .github/workflows/untrusted_checkout_no_needs.yml:31:9:31:23 | Run Step | -| .github/workflows/untrusted_checkout_paths.yml:11:9:15:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:15:9:17:2 | Run Step | -| .github/workflows/untrusted_checkout_paths.yml:20:9:24:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:24:9:26:2 | Run Step | -| .github/workflows/untrusted_checkout_paths.yml:29:9:33:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:33:9:35:2 | Uses Step | -| .github/workflows/untrusted_checkout_paths.yml:38:9:42:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:42:9:42:33 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:10:9:15:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:15:9:17:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:20:9:25:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:25:9:27:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:30:9:35:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:35:9:37:2 | Uses Step | +| .github/workflows/untrusted_checkout_paths.yml:40:9:45:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:45:9:47:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:50:9:55:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:55:9:57:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:60:9:65:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:65:9:67:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:70:9:75:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:75:9:77:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:80:9:85:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:85:9:87:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:90:9:95:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:95:9:97:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:100:9:105:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:105:9:107:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:110:9:115:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:115:9:117:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:120:9:125:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:125:9:127:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:130:9:135:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:135:9:137:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:140:9:145:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:145:9:147:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:150:9:155:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:155:9:157:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:160:9:165:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:165:9:167:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:170:9:175:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:175:9:177:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:180:9:185:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:185:9:187:2 | Uses Step | +| .github/workflows/untrusted_checkout_paths.yml:190:9:195:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:195:9:197:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:200:9:205:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:205:9:207:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:219:9:222:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:222:9:226:6 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:222:9:226:6 | Run Step | .github/workflows/untrusted_checkout_paths.yml:226:9:228:2 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:231:9:234:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:234:9:238:6 | Run Step | +| .github/workflows/untrusted_checkout_paths.yml:234:9:238:6 | Run Step | .github/workflows/untrusted_checkout_paths.yml:238:9:238:39 | Run Step | | .github/workflows/untrusted_checkout_permission_check_reusable2.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_permission_check_reusable2.yml:16:9:22:2 | Run Step | | .github/workflows/untrusted_checkout_permission_check_reusable.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_permission_check_reusable.yml:16:9:22:2 | Run Step | | .github/workflows/untrusted_checkout_permission_check_reusable_level2.yml:8:9:16:6 | Uses Step: checkAccess | .github/workflows/untrusted_checkout_permission_check_reusable_level2.yml:16:9:22:2 | Run Step | @@ -399,9 +419,20 @@ edges | .github/workflows/untrusted_checkout.yml:8:9:11:6 | Uses Step | .github/workflows/untrusted_checkout.yml:8:9:11:6 | Uses Step | .github/workflows/untrusted_checkout.yml:15:9:18:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout.yml:2:3:2:21 | pull_request_target | pull_request_target | | .github/workflows/untrusted_checkout.yml:23:9:26:6 | Uses Step | .github/workflows/untrusted_checkout.yml:23:9:26:6 | Uses Step | .github/workflows/untrusted_checkout.yml:30:9:32:23 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout.yml:2:3:2:21 | pull_request_target | pull_request_target | | .github/workflows/untrusted_checkout_no_needs.yml:26:9:31:6 | Uses Step | .github/workflows/untrusted_checkout_no_needs.yml:26:9:31:6 | Uses Step | .github/workflows/untrusted_checkout_no_needs.yml:31:9:31:23 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_no_needs.yml:2:3:2:21 | pull_request_target | pull_request_target | -| .github/workflows/untrusted_checkout_paths.yml:11:9:15:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:11:9:15:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:15:9:17:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:15 | issue_comment | issue_comment | -| .github/workflows/untrusted_checkout_paths.yml:20:9:24:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:20:9:24:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:24:9:26:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:15 | issue_comment | issue_comment | -| .github/workflows/untrusted_checkout_paths.yml:29:9:33:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:29:9:33:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:33:9:35:2 | Uses Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:15 | issue_comment | issue_comment | -| .github/workflows/untrusted_checkout_paths.yml:38:9:42:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:38:9:42:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:42:9:42:33 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:15 | issue_comment | issue_comment | +| .github/workflows/untrusted_checkout_paths.yml:10:9:15:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:10:9:15:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:15:9:17:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:20:9:25:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:20:9:25:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:25:9:27:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:30:9:35:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:30:9:35:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:35:9:37:2 | Uses Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:40:9:45:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:40:9:45:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:45:9:47:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:50:9:55:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:50:9:55:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:55:9:57:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:60:9:65:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:60:9:65:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:65:9:67:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:70:9:75:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:70:9:75:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:75:9:77:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:80:9:85:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:80:9:85:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:85:9:87:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:90:9:95:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:90:9:95:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:95:9:97:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:100:9:105:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:100:9:105:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:105:9:107:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:150:9:155:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:150:9:155:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:155:9:157:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:160:9:165:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:160:9:165:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:165:9:167:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:170:9:175:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:170:9:175:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:175:9:177:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:180:9:185:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:180:9:185:6 | Uses Step | .github/workflows/untrusted_checkout_paths.yml:185:9:187:2 | Uses Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | +| .github/workflows/untrusted_checkout_paths.yml:222:9:226:6 | Run Step | .github/workflows/untrusted_checkout_paths.yml:222:9:226:6 | Run Step | .github/workflows/untrusted_checkout_paths.yml:226:9:228:2 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | | .github/workflows/untrusted_checkout_permissions_check.yml:36:9:41:6 | Uses Step | .github/workflows/untrusted_checkout_permissions_check.yml:36:9:41:6 | Uses Step | .github/workflows/untrusted_checkout_permissions_check.yml:41:9:41:22 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_permissions_check.yml:2:3:2:21 | pull_request_target | pull_request_target | | .github/workflows/workflow_run_untrusted_checkout_path.yml:12:9:16:6 | Uses Step | .github/workflows/workflow_run_untrusted_checkout_path.yml:12:9:16:6 | Uses Step | .github/workflows/workflow_run_untrusted_checkout_path.yml:16:9:16:40 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/workflow_run_untrusted_checkout_path.yml:4:3:4:14 | workflow_run | workflow_run | diff --git a/actions/ql/test/query-tests/Security/CWE-829/UntrustedCheckoutHigh.expected b/actions/ql/test/query-tests/Security/CWE-829/UntrustedCheckoutHigh.expected index 9b9483f224e6..e541ebb6a80e 100644 --- a/actions/ql/test/query-tests/Security/CWE-829/UntrustedCheckoutHigh.expected +++ b/actions/ql/test/query-tests/Security/CWE-829/UntrustedCheckoutHigh.expected @@ -17,6 +17,7 @@ | .github/workflows/pr-workflow.yml:444:9:449:6 | Uses Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/pr-workflow-fork.yaml:7:3:7:21 | pull_request_target | pull_request_target | | .github/workflows/test13.yml:20:7:25:4 | Uses Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/test13.yml:2:3:2:15 | issue_comment | issue_comment | | .github/workflows/untrusted_checkout2.yml:14:9:19:72 | Run Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout2.yml:1:5:1:17 | issue_comment | issue_comment | +| .github/workflows/untrusted_checkout_paths.yml:210:9:216:2 | Uses Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/untrusted_checkout_paths.yml:4:3:4:21 | pull_request_target | pull_request_target | | .github/workflows/workflow_run_untrusted_checkout.yml:13:9:16:6 | Uses Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/workflow_run_untrusted_checkout.yml:2:3:2:14 | workflow_run | workflow_run | | .github/workflows/workflow_run_untrusted_checkout.yml:16:9:18:31 | Uses Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/workflow_run_untrusted_checkout.yml:2:3:2:14 | workflow_run | workflow_run | | .github/workflows/workflow_run_untrusted_checkout_2.yml:13:9:16:6 | Uses Step | Checkout of untrusted code in a privileged workflow with later potential execution (event trigger: $@). | .github/workflows/workflow_run_untrusted_checkout_2.yml:2:3:2:14 | workflow_run | workflow_run |