From dc2387baefc27bf00e6a8e6ecd2d4c6d3c8e07d7 Mon Sep 17 00:00:00 2001 From: JarLob Date: Thu, 17 Sep 2026 21:13:03 +0300 Subject: [PATCH] Update cache poisoning queries for the new cache-mode syntax https://github.blog/changelog/2026-09-10-control-github-actions-cache-access-with-cache-mode/ --- .../lib/change-notes/2026-09-17-cache-mode.md | 5 + actions/ql/lib/codeql/actions/Ast.qll | 6 + .../lib/codeql/actions/ast/internal/Ast.qll | 6 + .../actions/security/CachePoisoningQuery.qll | 72 +++++++- .../CWE-349/CachePoisoningViaCodeInjection.md | 22 ++- .../CWE-349/CachePoisoningViaDirectCache.md | 24 ++- .../CachePoisoningViaPoisonableStep.md | 23 ++- .../src/change-notes/2026-09-17-cache-mode.md | 5 + .../.github/workflows/cache_mode_nested.yml | 34 ++++ .../.github/workflows/cache_mode_reusable.yml | 42 +++++ .../cache_write_capable_pull_request.yml | 64 ++++++++ .../workflows/cache_write_capable_push.yml | 45 ++++- ...write_capable_reusable_workflow_caller.yml | 18 +- .../cache_write_capable_workflow_dispatch.yml | 36 +++- .../.github/workflows/code_injection1.yml | 22 ++- .../.github/workflows/direct_cache1.yml | 8 +- .../.github/workflows/poisonable_step1.yml | 14 +- .../CachePoisoningViaCodeInjection.expected | 20 ++- .../CachePoisoningViaDirectCache.expected | 40 +++-- .../CachePoisoningViaPoisonableStep.expected | 53 +++--- .../CWE-349/CacheWriteAccess.expected | 155 ++++++++++++++++++ .../Security/CWE-349/CacheWriteAccess.ql | 15 ++ 22 files changed, 656 insertions(+), 73 deletions(-) create mode 100644 actions/ql/lib/change-notes/2026-09-17-cache-mode.md create mode 100644 actions/ql/src/change-notes/2026-09-17-cache-mode.md create mode 100644 actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_mode_nested.yml create mode 100644 actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_mode_reusable.yml create mode 100644 actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_pull_request.yml create mode 100644 actions/ql/test/query-tests/Security/CWE-349/CacheWriteAccess.expected create mode 100644 actions/ql/test/query-tests/Security/CWE-349/CacheWriteAccess.ql diff --git a/actions/ql/lib/change-notes/2026-09-17-cache-mode.md b/actions/ql/lib/change-notes/2026-09-17-cache-mode.md new file mode 100644 index 000000000000..01992ea08db6 --- /dev/null +++ b/actions/ql/lib/change-notes/2026-09-17-cache-mode.md @@ -0,0 +1,5 @@ +--- +category: feature +--- +* Added `Workflow.getCacheMode()` and `Job.getCacheMode()` to read explicitly declared cache modes. The `hasDefaultBranchCacheWriteAccess` predicate now accounts for these modes, job overrides, and explicit caller limits in reusable workflows. +* `runsOnDefaultBranch` now includes unfiltered branch pushes and `pull_request` `closed` events that may refer to a merge into the default branch, while excluding tag-only pushes. diff --git a/actions/ql/lib/codeql/actions/Ast.qll b/actions/ql/lib/codeql/actions/Ast.qll index 6e76e4cd665a..92ff6becec41 100644 --- a/actions/ql/lib/codeql/actions/Ast.qll +++ b/actions/ql/lib/codeql/actions/Ast.qll @@ -104,6 +104,9 @@ class Workflow extends AstNode instanceof WorkflowImpl { Permissions getPermissions() { result = super.getPermissions() } + /** Gets the explicitly declared cache mode for this workflow, if any. */ + string getCacheMode() { result = super.getCacheMode() } + Strategy getStrategy() { result = super.getStrategy() } On getOn() { result = super.getOn() } @@ -200,6 +203,9 @@ abstract class Job extends AstNode instanceof JobImpl { Permissions getPermissions() { result = super.getPermissions() } + /** Gets the explicitly declared cache mode for this job, if any. */ + string getCacheMode() { result = super.getCacheMode() } + Strategy getStrategy() { result = super.getStrategy() } string getARunsOnLabel() { result = super.getARunsOnLabel() } diff --git a/actions/ql/lib/codeql/actions/ast/internal/Ast.qll b/actions/ql/lib/codeql/actions/ast/internal/Ast.qll index 82d4a1186708..96fe64bdfcdb 100644 --- a/actions/ql/lib/codeql/actions/ast/internal/Ast.qll +++ b/actions/ql/lib/codeql/actions/ast/internal/Ast.qll @@ -494,6 +494,9 @@ class WorkflowImpl extends AstNodeImpl, TWorkflowNode { /** Gets the permissions granted to this workflow. */ PermissionsImpl getPermissions() { result.getNode() = n.lookup("permissions") } + /** Gets the explicitly declared cache mode for this workflow, if any. */ + string getCacheMode() { result = n.lookup("cache-mode").(YamlString).getValue() } + /** Gets the trigger event that starts this workflow. */ override EventImpl getATriggerEvent() { this.getOn().getAnEvent() = result } @@ -923,6 +926,9 @@ class JobImpl extends AstNodeImpl, TJobNode { /** Gets the permissions for this job. */ PermissionsImpl getPermissions() { result.getNode() = n.lookup("permissions") } + /** Gets the explicitly declared cache mode for this job, if any. */ + string getCacheMode() { result = n.lookup("cache-mode").(YamlString).getValue() } + /** Gets the strategy for this job. */ StrategyImpl getStrategy() { result.getNode() = n.lookup("strategy") } diff --git a/actions/ql/lib/codeql/actions/security/CachePoisoningQuery.qll b/actions/ql/lib/codeql/actions/security/CachePoisoningQuery.qll index 41529c489ff0..80d040bb6327 100644 --- a/actions/ql/lib/codeql/actions/security/CachePoisoningQuery.qll +++ b/actions/ql/lib/codeql/actions/security/CachePoisoningQuery.qll @@ -10,15 +10,27 @@ string defaultBranchTriggerEvent() { ] } +/** + * Holds if `e` can run in the default-branch cache scope. + * A pull request closed by a merge uses its base branch instead of its merge ref. + */ predicate runsOnDefaultBranch(Event e) { ( e.getName() = defaultBranchTriggerEvent() and not e.getName() = "pull_request_target" or - e.getName() = "push" and - e.getAPropertyValue("branches") = defaultBranchNames() - or - e.getName() = "pull_request_target" and + ( + e.getName() = "push" and + ( + e.hasProperty(["branches", "branches-ignore"]) + or + not e.hasProperty(["tags", "tags-ignore"]) + ) + or + e.getName() = "pull_request_target" + or + e.getName() = "pull_request" and e.getAnActivityType() = "closed" + ) and ( // no filtering not e.hasProperty("branches") and not e.hasProperty("branches-ignore") @@ -50,17 +62,61 @@ private string defaultBranchCacheWriteEvent() { ] } -private predicate eventHasDefaultBranchCacheWriteAccess(Event event) { - runsOnDefaultBranch(event) and event.getName() = defaultBranchCacheWriteEvent() +private string getDeclaredCacheMode(Job job) { + result = job.getCacheMode() + or + not exists(job.getCacheMode()) and + result = job.getWorkflow().getCacheMode() +} + +private predicate cacheModeIsAllowed(string mode, string inheritedMode) { + mode = ["read", "write", "write-only", "none"] and + inheritedMode = ["default", "read", "write", "write-only", "none"] and + (inheritedMode = ["default", "write"] or mode = ["none", inheritedMode]) +} + +/** + * Gets the effective declared cache mode, or `default` if no mode was declared in the call chain. + * An event's implicit default does not limit the modes a reusable workflow can request. + * Both the callee's workflow-level mode and its job-level modes must fit the caller's explicit limit. + */ +private string getEffectiveCacheMode(Job job, Event event) { + exists(string inheritedMode | + ( + job.getWorkflow().getOn().getAnEvent() = event and + not event.getName() = "workflow_call" and + inheritedMode = "default" + or + inheritedMode = + getEffectiveCacheMode(job.getWorkflow().(ReusableWorkflow).getACaller(), event) + ) + | + ( + not exists(job.getWorkflow().getCacheMode()) + or + cacheModeIsAllowed(job.getWorkflow().getCacheMode(), inheritedMode) + ) and + if exists(getDeclaredCacheMode(job)) + then + result = getDeclaredCacheMode(job) and + cacheModeIsAllowed(result, inheritedMode) + else result = inheritedMode + ) } /** * Holds if `job` can write to the cache scope of the default branch for `event`. - * Reusable workflow jobs inherit their caller's trigger event. + * Job cache modes override workflow cache modes, subject to explicit limits from reusable workflow + * callers. Trigger-based defaults apply only if no cache mode was declared in the call chain. */ predicate hasDefaultBranchCacheWriteAccess(LocalJob job, Event event) { job.getATriggerEvent() = event and - eventHasDefaultBranchCacheWriteAccess(event) + runsOnDefaultBranch(event) and + exists(string mode | mode = getEffectiveCacheMode(job, event) | + mode = ["write", "write-only"] + or + mode = "default" and event.getName() = defaultBranchCacheWriteEvent() + ) } abstract class CacheWritingStep extends Step { diff --git a/actions/ql/src/Security/CWE-349/CachePoisoningViaCodeInjection.md b/actions/ql/src/Security/CWE-349/CachePoisoningViaCodeInjection.md index 0ef3199e0fd9..6d53cd7f3128 100644 --- a/actions/ql/src/Security/CWE-349/CachePoisoningViaCodeInjection.md +++ b/actions/ql/src/Security/CWE-349/CachePoisoningViaCodeInjection.md @@ -1,6 +1,6 @@ ## Overview -GitHub Actions cache poisoning is a technique that allows an attacker to inject malicious content into the Action's cache from unprivileged workflow, potentially leading to code execution in privileged workflows. +GitHub Actions cache poisoning is a technique that allows an attacker to inject malicious content into the Actions cache from an unprivileged workflow with cache-write access, potentially leading to code execution in privileged workflows. An attacker with the ability to run code in the context of the default branch (e.g. through Code Injection or Execution of Untrusted Code) can exploit this to: @@ -23,7 +23,13 @@ Due to the above design, if something is cached in the context of the default br ## Recommendation -1. Avoid using caching in workflows that handle sensitive operations like releases. +Set `cache-mode: read` on workflows or jobs that process untrusted input and only need to restore +caches, or `cache-mode: none` if they do not need cache access. These restrictions are enforced by +the cache service. Job-level settings override workflow-level settings. Set an explicit mode on +jobs that call reusable workflows to limit the access those workflows can request. Do not use +`write-only` to prevent cache poisoning: it prevents restores but still permits saves. + +1. Avoid restoring caches in workflows that handle sensitive operations like releases. Use `cache-mode: none` to disable cache access. 2. If caching must be used: - Validate restored cache contents before use. - Use short-lived, workflow-specific cache keys. @@ -34,9 +40,13 @@ Due to the above design, if something is cached in the context of the default br ## Example -GitHub gives workflows triggered by low-trust events, such as `issue_comment`, +By default, GitHub gives workflows triggered by low-trust events, such as `issue_comment`, `pull_request_target`, and `workflow_run`, read-only access to the default branch cache scope. -This query therefore reports only workflows whose trigger can write to that scope. +An explicit `cache-mode: write` or `cache-mode: write-only` grants write access even for these +triggers, while `read` and `none` prevent cache saves. Reusable workflows cannot exceed an explicit +mode set by their caller, but can override an implicit trigger-based default. +This query reports only jobs that can write to the default branch cache scope, accounting for +the trigger, branch, and effective cache mode. ### Incorrect Usage @@ -62,7 +72,7 @@ jobs: ### Correct Usage The following workflow passes the commit message through an environment variable, so the shell -does not interpret its contents as code. +does not interpret its contents as code. It also explicitly prevents cache saves with `cache-mode: read`. ```yaml name: Secure Workflow @@ -73,6 +83,7 @@ on: jobs: build: permissions: {} + cache-mode: read runs-on: ubuntu-latest steps: - env: @@ -85,4 +96,5 @@ jobs: - Adnan Khan's Blog: [The Monsters in Your Build Cache – GitHub Actions Cache Poisoning](https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/). - GitHub Docs: [Cache access for low-trust workflow triggers](https://docs.github.com/actions/reference/workflows-and-actions/dependency-caching#cache-access-for-low-trust-workflow-triggers). +- GitHub Docs: [Controlling cache access with cache-mode](https://docs.github.com/actions/reference/workflows-and-actions/dependency-caching#controlling-cache-access-with-cache-mode). - Scribe Security Blog: [Cache Poisoning in GitHub Actions](https://scribesecurity.com/blog/github-cache-poisoning/). diff --git a/actions/ql/src/Security/CWE-349/CachePoisoningViaDirectCache.md b/actions/ql/src/Security/CWE-349/CachePoisoningViaDirectCache.md index a22c41ad3e3a..9aab88aba8de 100644 --- a/actions/ql/src/Security/CWE-349/CachePoisoningViaDirectCache.md +++ b/actions/ql/src/Security/CWE-349/CachePoisoningViaDirectCache.md @@ -1,6 +1,6 @@ ## Overview -GitHub Actions cache poisoning is a technique that allows an attacker to inject malicious content into the Action's cache from unprivileged workflow, potentially leading to code execution in privileged workflows. +GitHub Actions cache poisoning is a technique that allows an attacker to inject malicious content into the Actions cache from an unprivileged workflow with cache-write access, potentially leading to code execution in privileged workflows. An attacker with the ability to run code in the context of the default branch (e.g. through Code Injection or Execution of Untrusted Code) can exploit this to: @@ -23,7 +23,13 @@ Due to the above design, if something is cached in the context of the default br ## Recommendation -1. Avoid using caching in workflows that handle sensitive operations like releases. +Set `cache-mode: read` on workflows or jobs that process untrusted input and only need to restore +caches, or `cache-mode: none` if they do not need cache access. These restrictions are enforced by +the cache service. Job-level settings override workflow-level settings. Set an explicit mode on +jobs that call reusable workflows to limit the access those workflows can request. Do not use +`write-only` to prevent cache poisoning: it prevents restores but still permits saves. + +1. Avoid restoring caches in workflows that handle sensitive operations like releases. Use `cache-mode: none` to disable cache access. 2. If caching must be used: - Validate restored cache contents before use. - Use short-lived, workflow-specific cache keys. @@ -34,9 +40,13 @@ Due to the above design, if something is cached in the context of the default br ## Example -GitHub gives workflows triggered by low-trust events, such as `issue_comment`, +By default, GitHub gives workflows triggered by low-trust events, such as `issue_comment`, `pull_request_target`, and `workflow_run`, read-only access to the default branch cache scope. -This query therefore reports only workflows whose trigger can write to that scope. +An explicit `cache-mode: write` or `cache-mode: write-only` grants write access even for these +triggers, while `read` and `none` prevent cache saves. Reusable workflows cannot exceed an explicit +mode set by their caller, but can override an implicit trigger-based default. +This query reports only jobs that can write to the default branch cache scope, accounting for +the trigger, branch, and effective cache mode. ### Incorrect Usage @@ -71,13 +81,16 @@ jobs: ### Correct Usage -The following workflow checking out untrusted files, but the cache is scoped to the Pull Request. +The following workflow checks out untrusted files in a pull request's isolated cache scope. +It also explicitly prevents cache saves with `cache-mode: read`. ```yaml name: Secure Workflow on: pull_request: +cache-mode: read + jobs: pr-comment: permissions: read-all @@ -99,4 +112,5 @@ jobs: - Adnan Khan's Blog: [The Monsters in Your Build Cache – GitHub Actions Cache Poisoning](https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/). - GitHub Docs: [Cache access for low-trust workflow triggers](https://docs.github.com/actions/reference/workflows-and-actions/dependency-caching#cache-access-for-low-trust-workflow-triggers). +- GitHub Docs: [Controlling cache access with cache-mode](https://docs.github.com/actions/reference/workflows-and-actions/dependency-caching#controlling-cache-access-with-cache-mode). - Scribe Security Blog: [Cache Poisoning in GitHub Actions](https://scribesecurity.com/blog/github-cache-poisoning/). diff --git a/actions/ql/src/Security/CWE-349/CachePoisoningViaPoisonableStep.md b/actions/ql/src/Security/CWE-349/CachePoisoningViaPoisonableStep.md index e5fd868609c4..51952788fa1e 100644 --- a/actions/ql/src/Security/CWE-349/CachePoisoningViaPoisonableStep.md +++ b/actions/ql/src/Security/CWE-349/CachePoisoningViaPoisonableStep.md @@ -1,6 +1,6 @@ ## Overview -GitHub Actions cache poisoning is a technique that allows an attacker to inject malicious content into the Action's cache from unprivileged workflow, potentially leading to code execution in privileged workflows. +GitHub Actions cache poisoning is a technique that allows an attacker to inject malicious content into the Actions cache from an unprivileged workflow with cache-write access, potentially leading to code execution in privileged workflows. An attacker with the ability to run code in the context of the default branch (e.g. through Code Injection or Execution of Untrusted Code) can exploit this to: @@ -23,7 +23,13 @@ Due to the above design, if something is cached in the context of the default br ## Recommendation -1. Avoid using caching in workflows that handle sensitive operations like releases. +Set `cache-mode: read` on workflows or jobs that process untrusted input and only need to restore +caches, or `cache-mode: none` if they do not need cache access. These restrictions are enforced by +the cache service. Job-level settings override workflow-level settings. Set an explicit mode on +jobs that call reusable workflows to limit the access those workflows can request. Do not use +`write-only` to prevent cache poisoning: it prevents restores but still permits saves. + +1. Avoid restoring caches in workflows that handle sensitive operations like releases. Use `cache-mode: none` to disable cache access. 2. If caching must be used: - Validate restored cache contents before use. - Use short-lived, workflow-specific cache keys. @@ -34,9 +40,13 @@ Due to the above design, if something is cached in the context of the default br ## Example -GitHub gives workflows triggered by low-trust events, such as `issue_comment`, +By default, GitHub gives workflows triggered by low-trust events, such as `issue_comment`, `pull_request_target`, and `workflow_run`, read-only access to the default branch cache scope. -This query therefore reports only workflows whose trigger can write to that scope. +An explicit `cache-mode: write` or `cache-mode: write-only` grants write access even for these +triggers, while `read` and `none` prevent cache saves. Reusable workflows cannot exceed an explicit +mode set by their caller, but can override an implicit trigger-based default. +This query reports only jobs that can write to the default branch cache scope, accounting for +the trigger, branch, and effective cache mode. ### Incorrect Usage @@ -67,7 +77,8 @@ jobs: ### Correct Usage -The following workflow runs untrusted code in a non-privileged job and the cache is scoped to the Pull Request branch. +The following workflow runs untrusted code in a non-privileged job with the cache scoped to the +pull request's merge ref. It also explicitly prevents cache saves with `cache-mode: read`. ```yaml name: Secure Workflow @@ -75,6 +86,7 @@ on: pull_request: branches: [main] permissions: {} +cache-mode: read jobs: test: runs-on: ubuntu-latest @@ -90,4 +102,5 @@ jobs: - Adnan Khan's Blog: [The Monsters in Your Build Cache – GitHub Actions Cache Poisoning](https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/). - GitHub Docs: [Cache access for low-trust workflow triggers](https://docs.github.com/actions/reference/workflows-and-actions/dependency-caching#cache-access-for-low-trust-workflow-triggers). +- GitHub Docs: [Controlling cache access with cache-mode](https://docs.github.com/actions/reference/workflows-and-actions/dependency-caching#controlling-cache-access-with-cache-mode). - Scribe Security Blog: [Cache Poisoning in GitHub Actions](https://scribesecurity.com/blog/github-cache-poisoning/). diff --git a/actions/ql/src/change-notes/2026-09-17-cache-mode.md b/actions/ql/src/change-notes/2026-09-17-cache-mode.md new file mode 100644 index 000000000000..57c4928cfdd1 --- /dev/null +++ b/actions/ql/src/change-notes/2026-09-17-cache-mode.md @@ -0,0 +1,5 @@ +--- +category: minorAnalysis +--- +* The cache-poisoning queries now honor workflow and job `cache-mode` settings, including overrides and explicit limits in reusable-workflow call chains. Jobs with `read` or `none` access are excluded, while low-trust triggers that explicitly request `write` or `write-only` access can now be reported. +* Cache-poisoning analysis now recognizes unfiltered pushes to the default branch and `pull_request` runs for merged `closed` events with write-capable cache modes. diff --git a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_mode_nested.yml b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_mode_nested.yml new file mode 100644 index 000000000000..401cfbd83563 --- /dev/null +++ b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_mode_nested.yml @@ -0,0 +1,34 @@ +on: + workflow_call: + workflow_dispatch: + push: # No cache alert: tag-only pushes cannot write the default-branch cache. + tags: ['v*'] + pull_request: # No cache alert: closed PRs target a feature branch. + types: [closed] + branches: [feature] + +permissions: {} + +jobs: + inherited: # No cache alert: harmless inherited-mode probe. + runs-on: ubuntu-latest + steps: + - run: echo test + + read: # No cache alert: read mode prevents saves and conflicts with write-only callers. + cache-mode: read + runs-on: ubuntu-latest + steps: + - run: echo test + + write: # No cache alert: harmless probe; write exceeds a write-only caller's limit. + cache-mode: write + runs-on: ubuntu-latest + steps: + - run: echo test + + write-only: # No cache alert: harmless probe of permitted cache saves. + cache-mode: write-only + runs-on: ubuntu-latest + steps: + - run: echo test diff --git a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_mode_reusable.yml b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_mode_reusable.yml new file mode 100644 index 000000000000..f7f32c34dcdf --- /dev/null +++ b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_mode_reusable.yml @@ -0,0 +1,42 @@ +on: + workflow_call: + pull_request: # No cache alert: ordinary PRs cannot write the default-branch cache. + push: # No cache alert: only feature-branch pushes match. + branches: [feature] + +permissions: {} +cache-mode: write + +jobs: + inherited: # No cache alert: harmless probe of workflow write mode within caller limits. + runs-on: ubuntu-latest + steps: + - run: echo test + + read: # No cache alert: read mode prevents saves. + cache-mode: read + runs-on: ubuntu-latest + steps: + - run: echo test + + none: # No cache alert: none mode disables cache access. + cache-mode: none + runs-on: ubuntu-latest + steps: + - run: echo test + + write: # No cache alert: harmless probe, even when caller limits permit writes. + cache-mode: write + runs-on: ubuntu-latest + steps: + - run: echo test + + write-only: # No cache alert: harmless probe; a job cannot rescue an over-limit workflow mode. + cache-mode: write-only + runs-on: ubuntu-latest + steps: + - run: echo test + + nested: + cache-mode: write-only + uses: ./.github/workflows/cache_mode_nested.yml # No cache alert: harmless probes under the nested write-only limit. diff --git a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_pull_request.yml b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_pull_request.yml new file mode 100644 index 000000000000..c18af0e0333f --- /dev/null +++ b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_pull_request.yml @@ -0,0 +1,64 @@ +on: + pull_request: + types: [closed] + branches: [main] + +permissions: {} + +jobs: + default: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.event.pull_request.title }}" # No cache alert: PR trigger defaults to read-only. + + read: + if: github.event.pull_request.merged == true + cache-mode: read + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.event.pull_request.title }}" # No cache alert: read mode prevents saves. + + none: + if: github.event.pull_request.merged == true + cache-mode: none + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.event.pull_request.title }}" # No cache alert: cache access is disabled. + + write: + if: github.event.pull_request.merged == true + cache-mode: write + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.event.pull_request.title }}" # Cache alert: title injection can poison the merged PR's base-branch cache. + - uses: actions/checkout@v4 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + - uses: actions/cache/save@v4 # Cache alert: saves untrusted PR files in the default-branch scope. + with: + path: . + key: merged-pr-write + - run: npm install # Cache alert: executes untrusted PR code with cache-write access. + + write-only: + if: github.event.pull_request.merged == true + cache-mode: write-only + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.event.pull_request.title }}" # Cache alert: write-only still allows saves after title injection. + - uses: actions/checkout@v4 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + - uses: actions/cache/save@v4 # Cache alert: write-only permits saving untrusted PR files. + with: + path: . + key: merged-pr-write-only + - run: npm install # Cache alert: untrusted PR code can save caches without restore access. + + reusable: + if: github.event.pull_request.merged == true + cache-mode: write + uses: ./.github/workflows/cache_mode_nested.yml # No cache alert: callee only probes access with harmless steps. diff --git a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_push.yml b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_push.yml index b9fbe2d2d0b4..aa240efa5ccf 100644 --- a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_push.yml +++ b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_push.yml @@ -1,10 +1,47 @@ -on: - push: - branches: [main] +on: push + +cache-mode: read jobs: injection: permissions: {} runs-on: ubuntu-latest steps: - - run: echo "${{ github.event.head_commit.message }}" \ No newline at end of file + - run: echo "${{ github.event.head_commit.message }}" # No cache alert: inherits workflow read mode. + + read: + cache-mode: read + permissions: {} + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.event.head_commit.message }}" # No cache alert: read mode prevents saves. + + none: + cache-mode: none + permissions: {} + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.event.head_commit.message }}" # No cache alert: cache access is disabled. + + write: + cache-mode: write + permissions: {} + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.event.head_commit.message }}" # Cache alert: write overrides read; unfiltered pushes include main. + + write-only: + cache-mode: write-only + permissions: {} + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.event.head_commit.message }}" # Cache alert: write-only permits saves after commit-message injection. + + inherited-cache: + permissions: {} + uses: ./.github/workflows/cache_write_capable_reusable_workflow.yml # No cache alert: caller read mode caps the callee. + + declared-cache: + cache-mode: write-only + permissions: {} + uses: ./.github/workflows/cache_mode_reusable.yml # No cache alert: callee write exceeds the write-only limit. \ No newline at end of file diff --git a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_reusable_workflow_caller.yml b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_reusable_workflow_caller.yml index 9f0f5841f89e..8a381638c926 100644 --- a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_reusable_workflow_caller.yml +++ b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_reusable_workflow_caller.yml @@ -1,16 +1,26 @@ on: - push: - branches: [main] + push: # Callee cache alerts expected: unfiltered pushes include main, writable by default. workflow_dispatch: inputs: head_sha: description: Commit SHA to test required: true type: string + issue_comment: # No default-mode callee cache alert: low-trust trigger is read-only. + types: [created] + pull_request_target: # No default-mode callee cache alert: low-trust trigger is read-only. + branches: [main] + workflow_run: # No default-mode callee cache alert: current model treats this trigger as read-only. + workflows: [Build] + types: [completed] jobs: reusable: permissions: {} - uses: ./.github/workflows/cache_write_capable_reusable_workflow.yml + uses: ./.github/workflows/cache_write_capable_reusable_workflow.yml # Callee alerts on push/dispatch: default cache access permits writes. with: - head_sha: ${{ github.event.inputs.head_sha }} \ No newline at end of file + head_sha: ${{ github.event.inputs.head_sha }} + + cache-modes: + permissions: {} + uses: ./.github/workflows/cache_mode_reusable.yml # No cache alert: harmless probes; unset caller mode allows callee write opt-in. \ No newline at end of file diff --git a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_workflow_dispatch.yml b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_workflow_dispatch.yml index 02bea71cfc61..a8d70057aac4 100644 --- a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_workflow_dispatch.yml +++ b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/cache_write_capable_workflow_dispatch.yml @@ -1,5 +1,7 @@ on: workflow_dispatch +cache-mode: none + jobs: cache: permissions: {} @@ -16,8 +18,38 @@ jobs: run: | git fetch origin "$HEAD_SHA" git checkout "$HEAD_SHA" - - run: npm install - - uses: actions/cache@v4 + - run: npm install # No cache alert: inherits workflow none mode. + - uses: actions/cache@v4 # No cache alert: inherited none mode prevents saves. with: path: .npm key: workflow-dispatch + + cache-write: + cache-mode: write + permissions: {} + runs-on: ubuntu-latest + steps: + - id: pr + env: + HEAD_SHA: ${{ github.event.inputs.head_sha }} + run: | + jq -cn --arg sha "$HEAD_SHA" '{head: {sha: $sha}}' | + sed 's/^/json=/' >> "$GITHUB_OUTPUT" + - env: + HEAD_SHA: ${{ fromJSON(steps.pr.outputs.json).head.sha }} + run: | + git fetch origin "$HEAD_SHA" + git checkout "$HEAD_SHA" + - run: npm install # Cache alert: job write override lets untrusted code save caches. + - uses: actions/cache@v4 # Cache alert: job write override permits caching untrusted files. + with: + path: .npm + key: workflow-dispatch-write + + inherited-cache: + permissions: {} + uses: ./.github/workflows/cache_write_capable_reusable_workflow.yml # No cache alert: inherits the caller's none mode. + + cache-modes: + permissions: {} + uses: ./.github/workflows/cache_mode_reusable.yml # No cache alert: callee write exceeds the caller's none limit. diff --git a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/code_injection1.yml b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/code_injection1.yml index 9f19634abc92..7ea374edb6e4 100644 --- a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/code_injection1.yml +++ b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/code_injection1.yml @@ -2,11 +2,31 @@ on: issue_comment: types: [created] +cache-mode: read + jobs: pr-comment: permissions: read-all runs-on: ubuntu-latest steps: - - run: | + - run: | # No cache alert: inherits workflow read mode. echo ${{ github.event.comment.body }} + write: + cache-mode: write + permissions: read-all + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.event.comment.body }}" # Cache alert: explicit write enables saves after comment injection. + + write-only: + cache-mode: write-only + permissions: read-all + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.event.comment.body }}" # Cache alert: write-only overrides the low-trust read default. + + cache-modes: + permissions: {} + uses: ./.github/workflows/cache_mode_reusable.yml # No cache alert: callee write exceeds the caller's read limit. + diff --git a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/direct_cache1.yml b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/direct_cache1.yml index 55efe8e9fec9..9c1f2a8ea4b9 100644 --- a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/direct_cache1.yml +++ b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/direct_cache1.yml @@ -2,6 +2,8 @@ on: issue_comment: types: [created] +cache-mode: write + jobs: pr-comment: permissions: read-all @@ -15,9 +17,13 @@ jobs: with: ref: ${{ steps.comment-branch.outputs.head_sha }} - - uses: actions/cache@v2 + - uses: actions/cache@v2 # Cache alert: workflow write mode allows saving untrusted PR files. with: path: ./poison key: poison_key - run: | cat poison + + cache-modes: + permissions: {} + uses: ./.github/workflows/cache_mode_reusable.yml # No cache alert: harmless access probes, despite write permission. diff --git a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/poisonable_step1.yml b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/poisonable_step1.yml index 05f8e4a067a1..585dc719981b 100644 --- a/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/poisonable_step1.yml +++ b/actions/ql/test/query-tests/Security/CWE-349/.github/workflows/poisonable_step1.yml @@ -2,6 +2,8 @@ on: issue_comment: types: [created] +cache-mode: write-only + jobs: pr-comment: runs-on: ubuntu-latest @@ -12,9 +14,10 @@ jobs: - uses: actions/checkout@v3 with: ref: ${{ steps.comment-branch.outputs.head_sha }} - - run: ./checkedout/poison + - run: ./checkedout/poison # Cache alert: untrusted code inherits write-only cache access. pr-comment2: + cache-mode: read runs-on: ubuntu-latest permissions: read-all steps: @@ -23,9 +26,10 @@ jobs: - uses: actions/checkout@v3 with: ref: ${{ steps.comment-branch.outputs.head_sha }} - - uses: ./.github/actions/node-npm-setup + - uses: ./.github/actions/node-npm-setup # No cache alert: job read mode prevents saves. pr-comment3: + cache-mode: none runs-on: ubuntu-latest permissions: read-all steps: @@ -34,4 +38,8 @@ jobs: - uses: actions/checkout@v3 with: ref: ${{ steps.comment-branch.outputs.head_sha }} - - run: node .github/actions-scripts/what-docs-early-access-branch.js + - run: node .github/actions-scripts/what-docs-early-access-branch.js # No cache alert: job none mode disables cache access. + + cache-modes: + permissions: {} + uses: ./.github/workflows/cache_mode_reusable.yml # No cache alert: callee write exceeds the write-only limit. diff --git a/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaCodeInjection.expected b/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaCodeInjection.expected index 86b2e48c25e0..48e092ff2ff4 100644 --- a/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaCodeInjection.expected +++ b/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaCodeInjection.expected @@ -1,13 +1,29 @@ edges | .github/workflows/code_injection2.yml:12:9:16:6 | Uses Step: modified_files | .github/workflows/code_injection2.yml:16:21:16:70 | steps.modified_files.outputs.files_modified | provenance | | nodes +| .github/workflows/cache_write_capable_pull_request.yml:13:21:13:58 | github.event.pull_request.title | semmle.label | github.event.pull_request.title | +| .github/workflows/cache_write_capable_pull_request.yml:20:21:20:58 | github.event.pull_request.title | semmle.label | github.event.pull_request.title | +| .github/workflows/cache_write_capable_pull_request.yml:27:21:27:58 | github.event.pull_request.title | semmle.label | github.event.pull_request.title | +| .github/workflows/cache_write_capable_pull_request.yml:34:21:34:58 | github.event.pull_request.title | semmle.label | github.event.pull_request.title | +| .github/workflows/cache_write_capable_pull_request.yml:50:21:50:58 | github.event.pull_request.title | semmle.label | github.event.pull_request.title | | .github/workflows/cache_write_capable_push.yml:10:21:10:59 | github.event.head_commit.message | semmle.label | github.event.head_commit.message | +| .github/workflows/cache_write_capable_push.yml:17:21:17:59 | github.event.head_commit.message | semmle.label | github.event.head_commit.message | +| .github/workflows/cache_write_capable_push.yml:24:21:24:59 | github.event.head_commit.message | semmle.label | github.event.head_commit.message | +| .github/workflows/cache_write_capable_push.yml:31:21:31:59 | github.event.head_commit.message | semmle.label | github.event.head_commit.message | +| .github/workflows/cache_write_capable_push.yml:38:21:38:59 | github.event.head_commit.message | semmle.label | github.event.head_commit.message | | .github/workflows/cache_write_capable_reusable_workflow.yml:45:21:45:59 | github.event.head_commit.message | semmle.label | github.event.head_commit.message | -| .github/workflows/code_injection1.yml:11:17:11:48 | github.event.comment.body | semmle.label | github.event.comment.body | +| .github/workflows/code_injection1.yml:13:17:13:48 | github.event.comment.body | semmle.label | github.event.comment.body | +| .github/workflows/code_injection1.yml:20:21:20:52 | github.event.comment.body | semmle.label | github.event.comment.body | +| .github/workflows/code_injection1.yml:27:21:27:52 | github.event.comment.body | semmle.label | github.event.comment.body | | .github/workflows/code_injection2.yml:12:9:16:6 | Uses Step: modified_files | semmle.label | Uses Step: modified_files | | .github/workflows/code_injection2.yml:16:21:16:70 | steps.modified_files.outputs.files_modified | semmle.label | steps.modified_files.outputs.files_modified | | .github/workflows/neg_code_injection1.yml:11:17:11:48 | github.event.comment.body | semmle.label | github.event.comment.body | subpaths #select -| .github/workflows/cache_write_capable_push.yml:10:21:10:59 | github.event.head_commit.message | .github/workflows/cache_write_capable_push.yml:10:21:10:59 | github.event.head_commit.message | .github/workflows/cache_write_capable_push.yml:10:21:10:59 | github.event.head_commit.message | Code injection in $@ may allow poisoning the default-branch cache (event trigger: $@). | .github/workflows/cache_write_capable_push.yml:10:21:10:59 | github.event.head_commit.message | ${{ github.event.head_commit.message }} | .github/workflows/cache_write_capable_push.yml:2:3:2:6 | push | push | +| .github/workflows/cache_write_capable_pull_request.yml:34:21:34:58 | github.event.pull_request.title | .github/workflows/cache_write_capable_pull_request.yml:34:21:34:58 | github.event.pull_request.title | .github/workflows/cache_write_capable_pull_request.yml:34:21:34:58 | github.event.pull_request.title | Code injection in $@ may allow poisoning the default-branch cache (event trigger: $@). | .github/workflows/cache_write_capable_pull_request.yml:34:21:34:58 | github.event.pull_request.title | ${{ github.event.pull_request.title }} | .github/workflows/cache_write_capable_pull_request.yml:2:3:2:14 | pull_request | pull_request | +| .github/workflows/cache_write_capable_pull_request.yml:50:21:50:58 | github.event.pull_request.title | .github/workflows/cache_write_capable_pull_request.yml:50:21:50:58 | github.event.pull_request.title | .github/workflows/cache_write_capable_pull_request.yml:50:21:50:58 | github.event.pull_request.title | Code injection in $@ may allow poisoning the default-branch cache (event trigger: $@). | .github/workflows/cache_write_capable_pull_request.yml:50:21:50:58 | github.event.pull_request.title | ${{ github.event.pull_request.title }} | .github/workflows/cache_write_capable_pull_request.yml:2:3:2:14 | pull_request | pull_request | +| .github/workflows/cache_write_capable_push.yml:31:21:31:59 | github.event.head_commit.message | .github/workflows/cache_write_capable_push.yml:31:21:31:59 | github.event.head_commit.message | .github/workflows/cache_write_capable_push.yml:31:21:31:59 | github.event.head_commit.message | Code injection in $@ may allow poisoning the default-branch cache (event trigger: $@). | .github/workflows/cache_write_capable_push.yml:31:21:31:59 | github.event.head_commit.message | ${{ github.event.head_commit.message }} | .github/workflows/cache_write_capable_push.yml:1:5:1:8 | push | push | +| .github/workflows/cache_write_capable_push.yml:38:21:38:59 | github.event.head_commit.message | .github/workflows/cache_write_capable_push.yml:38:21:38:59 | github.event.head_commit.message | .github/workflows/cache_write_capable_push.yml:38:21:38:59 | github.event.head_commit.message | Code injection in $@ may allow poisoning the default-branch cache (event trigger: $@). | .github/workflows/cache_write_capable_push.yml:38:21:38:59 | github.event.head_commit.message | ${{ github.event.head_commit.message }} | .github/workflows/cache_write_capable_push.yml:1:5:1:8 | push | push | | .github/workflows/cache_write_capable_reusable_workflow.yml:45:21:45:59 | github.event.head_commit.message | .github/workflows/cache_write_capable_reusable_workflow.yml:45:21:45:59 | github.event.head_commit.message | .github/workflows/cache_write_capable_reusable_workflow.yml:45:21:45:59 | github.event.head_commit.message | Code injection in $@ may allow poisoning the default-branch cache (event trigger: $@). | .github/workflows/cache_write_capable_reusable_workflow.yml:45:21:45:59 | github.event.head_commit.message | ${{ github.event.head_commit.message }} | .github/workflows/cache_write_capable_reusable_workflow_caller.yml:2:3:2:6 | push | push | +| .github/workflows/code_injection1.yml:20:21:20:52 | github.event.comment.body | .github/workflows/code_injection1.yml:20:21:20:52 | github.event.comment.body | .github/workflows/code_injection1.yml:20:21:20:52 | github.event.comment.body | Code injection in $@ may allow poisoning the default-branch cache (event trigger: $@). | .github/workflows/code_injection1.yml:20:21:20:52 | github.event.comment.body | ${{ github.event.comment.body }} | .github/workflows/code_injection1.yml:2:3:2:15 | issue_comment | issue_comment | +| .github/workflows/code_injection1.yml:27:21:27:52 | github.event.comment.body | .github/workflows/code_injection1.yml:27:21:27:52 | github.event.comment.body | .github/workflows/code_injection1.yml:27:21:27:52 | github.event.comment.body | Code injection in $@ may allow poisoning the default-branch cache (event trigger: $@). | .github/workflows/code_injection1.yml:27:21:27:52 | github.event.comment.body | ${{ github.event.comment.body }} | .github/workflows/code_injection1.yml:2:3:2:15 | issue_comment | issue_comment | 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 7c7fc96dae52..be6dc4e6028f 100644 --- a/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaDirectCache.expected +++ b/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaDirectCache.expected @@ -1,18 +1,27 @@ edges +| .github/workflows/cache_write_capable_pull_request.yml:34:9:35:6 | Run Step | .github/workflows/cache_write_capable_pull_request.yml:35:9:39:6 | Uses Step | +| .github/workflows/cache_write_capable_pull_request.yml:35:9:39:6 | Uses Step | .github/workflows/cache_write_capable_pull_request.yml:39:9:43:6 | Uses Step | +| .github/workflows/cache_write_capable_pull_request.yml:39:9:43:6 | Uses Step | .github/workflows/cache_write_capable_pull_request.yml:43:9:45:2 | Run Step | +| .github/workflows/cache_write_capable_pull_request.yml:50:9:51:6 | Run Step | .github/workflows/cache_write_capable_pull_request.yml:51:9:55:6 | Uses Step | +| .github/workflows/cache_write_capable_pull_request.yml:51:9:55:6 | Uses Step | .github/workflows/cache_write_capable_pull_request.yml:55:9:59:6 | Uses Step | +| .github/workflows/cache_write_capable_pull_request.yml:55:9:59:6 | Uses Step | .github/workflows/cache_write_capable_pull_request.yml:59:9:61:2 | Run Step | | .github/workflows/cache_write_capable_reusable_workflow.yml:14:9:15:6 | Uses Step | .github/workflows/cache_write_capable_reusable_workflow.yml:15:9:21:6 | Run Step | | .github/workflows/cache_write_capable_reusable_workflow.yml:15:9:21:6 | Run Step | .github/workflows/cache_write_capable_reusable_workflow.yml:21:9:26:2 | Uses Step | | .github/workflows/cache_write_capable_reusable_workflow.yml:31:9:32:6 | Uses Step | .github/workflows/cache_write_capable_reusable_workflow.yml:32:9:38:6 | Run Step | | .github/workflows/cache_write_capable_reusable_workflow.yml:32:9:38:6 | Run Step | .github/workflows/cache_write_capable_reusable_workflow.yml:38:9:40:2 | Run Step | -| .github/workflows/cache_write_capable_workflow_dispatch.yml:8:9:14:6 | Run Step: pr | .github/workflows/cache_write_capable_workflow_dispatch.yml:14:9:19:6 | Run Step | -| .github/workflows/cache_write_capable_workflow_dispatch.yml:14:9:19:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:19:9:20:6 | Run Step | -| .github/workflows/cache_write_capable_workflow_dispatch.yml:19:9:20:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:20:9:23:33 | Uses Step | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:10:9:16:6 | Run Step: pr | .github/workflows/cache_write_capable_workflow_dispatch.yml:16:9:21:6 | Run Step | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:16:9:21:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:21:9:22:6 | Run Step | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:21:9:22:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:22:9:27:2 | Uses Step | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:32:9:38:6 | Run Step: pr | .github/workflows/cache_write_capable_workflow_dispatch.yml:38:9:43:6 | Run Step | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:38:9:43:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:43:9:44:6 | Run Step | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:43:9:44:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:44:9:49:2 | Uses Step | | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:14:9:15:6 | Uses Step | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:15:9:21:6 | Run Step | | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:15:9:21:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:21:9:22:6 | Run Step | | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:21:9:22:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:22:9:25:32 | Uses Step | | .github/workflows/code_injection2.yml:12:9:16:6 | Uses Step: modified_files | .github/workflows/code_injection2.yml:16:9:16:71 | Run Step | -| .github/workflows/direct_cache1.yml:10:9:13:6 | Uses Step: comment-branch | .github/workflows/direct_cache1.yml:13:9:18:6 | Uses Step | -| .github/workflows/direct_cache1.yml:13:9:18:6 | Uses Step | .github/workflows/direct_cache1.yml:18:9:22:6 | Uses Step | -| .github/workflows/direct_cache1.yml:18:9:22:6 | Uses Step | .github/workflows/direct_cache1.yml:22:9:23:21 | Run Step | +| .github/workflows/direct_cache1.yml:12:9:15:6 | Uses Step: comment-branch | .github/workflows/direct_cache1.yml:15:9:20:6 | Uses Step | +| .github/workflows/direct_cache1.yml:15:9:20:6 | Uses Step | .github/workflows/direct_cache1.yml:20:9:24:6 | Uses Step | +| .github/workflows/direct_cache1.yml:20:9:24:6 | Uses Step | .github/workflows/direct_cache1.yml:24:9:27:2 | Run Step | | .github/workflows/direct_cache2.yml:11:9:14:6 | Uses Step | .github/workflows/direct_cache2.yml:14:9:18:6 | Uses Step | | .github/workflows/direct_cache2.yml:14:9:18:6 | Uses Step | .github/workflows/direct_cache2.yml:18:9:19:21 | Run Step | | .github/workflows/direct_cache3.yml:11:9:14:6 | Uses Step: comment-branch | .github/workflows/direct_cache3.yml:14:9:19:6 | Uses Step | @@ -41,12 +50,12 @@ edges | .github/workflows/neg_poisonable_step1.yml:11:9:14:6 | Uses Step: comment-branch | .github/workflows/neg_poisonable_step1.yml:14:9:19:6 | Uses Step | | .github/workflows/neg_poisonable_step1.yml:14:9:19:6 | Uses Step | .github/workflows/neg_poisonable_step1.yml:19:9:20:30 | Run Step | | .github/workflows/neg_poisonable_step2.yml:13:9:16:6 | Uses Step | .github/workflows/neg_poisonable_step2.yml:16:9:17:54 | Run Step | -| .github/workflows/poisonable_step1.yml:10:9:12:6 | Uses Step: comment-branch | .github/workflows/poisonable_step1.yml:12:9:15:6 | Uses Step | -| .github/workflows/poisonable_step1.yml:12:9:15:6 | Uses Step | .github/workflows/poisonable_step1.yml:15:9:17:2 | Run Step | -| .github/workflows/poisonable_step1.yml:21:9:23:6 | Uses Step: comment-branch | .github/workflows/poisonable_step1.yml:23:9:26:6 | Uses Step | -| .github/workflows/poisonable_step1.yml:23:9:26:6 | Uses Step | .github/workflows/poisonable_step1.yml:26:9:28:2 | Uses Step | -| .github/workflows/poisonable_step1.yml:32:9:34:6 | Uses Step: comment-branch | .github/workflows/poisonable_step1.yml:34:9:37:6 | Uses Step | -| .github/workflows/poisonable_step1.yml:34:9:37:6 | Uses Step | .github/workflows/poisonable_step1.yml:37:9:37:75 | Run Step | +| .github/workflows/poisonable_step1.yml:12:9:14:6 | Uses Step: comment-branch | .github/workflows/poisonable_step1.yml:14:9:17:6 | Uses Step | +| .github/workflows/poisonable_step1.yml:14:9:17:6 | Uses Step | .github/workflows/poisonable_step1.yml:17:9:19:2 | Run Step | +| .github/workflows/poisonable_step1.yml:24:9:26:6 | Uses Step: comment-branch | .github/workflows/poisonable_step1.yml:26:9:29:6 | Uses Step | +| .github/workflows/poisonable_step1.yml:26:9:29:6 | Uses Step | .github/workflows/poisonable_step1.yml:29:9:31:2 | Uses Step | +| .github/workflows/poisonable_step1.yml:36:9:38:6 | Uses Step: comment-branch | .github/workflows/poisonable_step1.yml:38:9:41:6 | Uses Step | +| .github/workflows/poisonable_step1.yml:38:9:41:6 | Uses Step | .github/workflows/poisonable_step1.yml:41:9:43:2 | Run Step | | .github/workflows/poisonable_step2.yml:15:9:20:6 | Uses Step | .github/workflows/poisonable_step2.yml:20:9:22:6 | Uses Step | | .github/workflows/poisonable_step2.yml:20:9:22:6 | Uses Step | .github/workflows/poisonable_step2.yml:22:9:26:31 | Uses Step | | .github/workflows/poisonable_step3.yml:13:7:19:4 | Uses Step | .github/workflows/poisonable_step3.yml:19:7:19:32 | Run Step | @@ -54,6 +63,9 @@ edges | .github/workflows/poisonable_step5.yml:17:9:22:6 | Uses Step | .github/workflows/poisonable_step5.yml:22:9:24:6 | Uses Step | | .github/workflows/poisonable_step5.yml:22:9:24:6 | Uses Step | .github/workflows/poisonable_step5.yml:24:9:28:31 | Uses Step | #select -| .github/workflows/cache_write_capable_reusable_workflow.yml:21:9:26:2 | Uses Step | .github/workflows/cache_write_capable_reusable_workflow.yml:15:9:21:6 | Run Step | .github/workflows/cache_write_capable_reusable_workflow.yml:21:9:26:2 | Uses Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code. ($@). | .github/workflows/cache_write_capable_reusable_workflow_caller.yml:4:3:4:19 | workflow_dispatch | workflow_dispatch | -| .github/workflows/cache_write_capable_workflow_dispatch.yml:20:9:23:33 | Uses Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:14:9:19:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:20:9:23:33 | Uses Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code. ($@). | .github/workflows/cache_write_capable_workflow_dispatch.yml:1:5:1:21 | workflow_dispatch | workflow_dispatch | +| .github/workflows/cache_write_capable_pull_request.yml:39:9:43:6 | Uses Step | .github/workflows/cache_write_capable_pull_request.yml:35:9:39:6 | Uses Step | .github/workflows/cache_write_capable_pull_request.yml:39:9:43:6 | Uses Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code. ($@). | .github/workflows/cache_write_capable_pull_request.yml:2:3:2:14 | pull_request | pull_request | +| .github/workflows/cache_write_capable_pull_request.yml:55:9:59:6 | Uses Step | .github/workflows/cache_write_capable_pull_request.yml:51:9:55:6 | Uses Step | .github/workflows/cache_write_capable_pull_request.yml:55:9:59:6 | Uses Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code. ($@). | .github/workflows/cache_write_capable_pull_request.yml:2:3:2:14 | pull_request | pull_request | +| .github/workflows/cache_write_capable_reusable_workflow.yml:21:9:26:2 | Uses Step | .github/workflows/cache_write_capable_reusable_workflow.yml:15:9:21:6 | Run Step | .github/workflows/cache_write_capable_reusable_workflow.yml:21:9:26:2 | Uses Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code. ($@). | .github/workflows/cache_write_capable_reusable_workflow_caller.yml:3:3:3:19 | workflow_dispatch | workflow_dispatch | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:44:9:49:2 | Uses Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:38:9:43:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:44:9:49:2 | Uses Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code. ($@). | .github/workflows/cache_write_capable_workflow_dispatch.yml:1:5:1:21 | workflow_dispatch | workflow_dispatch | | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:22:9:25:32 | Uses Step | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:15:9:21:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:22:9:25:32 | Uses Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code. ($@). | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:2:3:2:19 | workflow_dispatch | workflow_dispatch | +| .github/workflows/direct_cache1.yml:20:9:24:6 | Uses Step | .github/workflows/direct_cache1.yml:15:9:20:6 | Uses Step | .github/workflows/direct_cache1.yml:20:9:24:6 | Uses Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code. ($@). | .github/workflows/direct_cache1.yml:2:3:2:15 | issue_comment | issue_comment | 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 59ad18242986..1e8f6ee03a56 100644 --- a/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaPoisonableStep.expected +++ b/actions/ql/test/query-tests/Security/CWE-349/CachePoisoningViaPoisonableStep.expected @@ -1,23 +1,35 @@ edges +| .github/workflows/cache_write_capable_pull_request.yml:34:9:35:6 | Run Step | .github/workflows/cache_write_capable_pull_request.yml:35:9:39:6 | Uses Step | +| .github/workflows/cache_write_capable_pull_request.yml:35:9:39:6 | Uses Step | .github/workflows/cache_write_capable_pull_request.yml:39:9:43:6 | Uses Step | +| .github/workflows/cache_write_capable_pull_request.yml:38:17:38:57 | github.event.pull_request.head.ref | .github/workflows/cache_write_capable_pull_request.yml:35:9:39:6 | Uses Step | +| .github/workflows/cache_write_capable_pull_request.yml:39:9:43:6 | Uses Step | .github/workflows/cache_write_capable_pull_request.yml:43:9:45:2 | Run Step | +| .github/workflows/cache_write_capable_pull_request.yml:50:9:51:6 | Run Step | .github/workflows/cache_write_capable_pull_request.yml:51:9:55:6 | Uses Step | +| .github/workflows/cache_write_capable_pull_request.yml:51:9:55:6 | Uses Step | .github/workflows/cache_write_capable_pull_request.yml:55:9:59:6 | Uses Step | +| .github/workflows/cache_write_capable_pull_request.yml:54:17:54:57 | github.event.pull_request.head.ref | .github/workflows/cache_write_capable_pull_request.yml:51:9:55:6 | Uses Step | +| .github/workflows/cache_write_capable_pull_request.yml:55:9:59:6 | Uses Step | .github/workflows/cache_write_capable_pull_request.yml:59:9:61:2 | Run Step | | .github/workflows/cache_write_capable_reusable_workflow.yml:14:9:15:6 | Uses Step | .github/workflows/cache_write_capable_reusable_workflow.yml:15:9:21:6 | Run Step | | .github/workflows/cache_write_capable_reusable_workflow.yml:15:9:21:6 | Run Step | .github/workflows/cache_write_capable_reusable_workflow.yml:21:9:26:2 | Uses Step | | .github/workflows/cache_write_capable_reusable_workflow.yml:16:22:16:43 | inputs.head_sha | .github/workflows/cache_write_capable_reusable_workflow.yml:15:9:21:6 | Run Step | | .github/workflows/cache_write_capable_reusable_workflow.yml:31:9:32:6 | Uses Step | .github/workflows/cache_write_capable_reusable_workflow.yml:32:9:38:6 | Run Step | | .github/workflows/cache_write_capable_reusable_workflow.yml:32:9:38:6 | Run Step | .github/workflows/cache_write_capable_reusable_workflow.yml:38:9:40:2 | Run Step | | .github/workflows/cache_write_capable_reusable_workflow.yml:33:22:33:43 | inputs.head_sha | .github/workflows/cache_write_capable_reusable_workflow.yml:32:9:38:6 | Run Step | -| .github/workflows/cache_write_capable_workflow_dispatch.yml:8:9:14:6 | Run Step: pr | .github/workflows/cache_write_capable_workflow_dispatch.yml:14:9:19:6 | Run Step | -| .github/workflows/cache_write_capable_workflow_dispatch.yml:14:9:19:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:19:9:20:6 | Run Step | -| .github/workflows/cache_write_capable_workflow_dispatch.yml:15:22:15:68 | fromJSON(steps.pr.outputs.json).head.sha | .github/workflows/cache_write_capable_workflow_dispatch.yml:14:9:19:6 | Run Step | -| .github/workflows/cache_write_capable_workflow_dispatch.yml:19:9:20:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:20:9:23:33 | Uses Step | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:10:9:16:6 | Run Step: pr | .github/workflows/cache_write_capable_workflow_dispatch.yml:16:9:21:6 | Run Step | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:16:9:21:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:21:9:22:6 | Run Step | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:17:22:17:68 | fromJSON(steps.pr.outputs.json).head.sha | .github/workflows/cache_write_capable_workflow_dispatch.yml:16:9:21:6 | Run Step | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:21:9:22:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:22:9:27:2 | Uses Step | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:32:9:38:6 | Run Step: pr | .github/workflows/cache_write_capable_workflow_dispatch.yml:38:9:43:6 | Run Step | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:38:9:43:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:43:9:44:6 | Run Step | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:39:22:39:68 | fromJSON(steps.pr.outputs.json).head.sha | .github/workflows/cache_write_capable_workflow_dispatch.yml:38:9:43:6 | Run Step | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:43:9:44:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:44:9:49:2 | Uses Step | | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:14:9:15:6 | Uses Step | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:15:9:21:6 | Run Step | | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:15:9:21:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:21:9:22:6 | Run Step | | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:16:22:16:56 | github.event.inputs.head_sha | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:15:9:21:6 | Run Step | | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:21:9:22:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:22:9:25:32 | Uses Step | | .github/workflows/code_injection2.yml:12:9:16:6 | Uses Step: modified_files | .github/workflows/code_injection2.yml:16:9:16:71 | Run Step | -| .github/workflows/direct_cache1.yml:10:9:13:6 | Uses Step: comment-branch | .github/workflows/direct_cache1.yml:13:9:18:6 | Uses Step | -| .github/workflows/direct_cache1.yml:13:9:18:6 | Uses Step | .github/workflows/direct_cache1.yml:18:9:22:6 | Uses Step | -| .github/workflows/direct_cache1.yml:16:17:16:60 | steps.comment-branch.outputs.head_sha | .github/workflows/direct_cache1.yml:13:9:18:6 | Uses Step | -| .github/workflows/direct_cache1.yml:18:9:22:6 | Uses Step | .github/workflows/direct_cache1.yml:22:9:23:21 | Run Step | +| .github/workflows/direct_cache1.yml:12:9:15:6 | Uses Step: comment-branch | .github/workflows/direct_cache1.yml:15:9:20:6 | Uses Step | +| .github/workflows/direct_cache1.yml:15:9:20:6 | Uses Step | .github/workflows/direct_cache1.yml:20:9:24:6 | Uses Step | +| .github/workflows/direct_cache1.yml:18:17:18:60 | steps.comment-branch.outputs.head_sha | .github/workflows/direct_cache1.yml:15:9:20:6 | Uses Step | +| .github/workflows/direct_cache1.yml:20:9:24:6 | Uses Step | .github/workflows/direct_cache1.yml:24:9:27:2 | Run Step | | .github/workflows/direct_cache2.yml:11:9:14:6 | Uses Step | .github/workflows/direct_cache2.yml:14:9:18:6 | Uses Step | | .github/workflows/direct_cache2.yml:13:17:13:57 | github.event.pull_request.head.sha | .github/workflows/direct_cache2.yml:11:9:14:6 | Uses Step | | .github/workflows/direct_cache2.yml:14:9:18:6 | Uses Step | .github/workflows/direct_cache2.yml:18:9:19:21 | Run Step | @@ -56,15 +68,15 @@ edges | .github/workflows/neg_poisonable_step1.yml:14:9:19:6 | Uses Step | .github/workflows/neg_poisonable_step1.yml:19:9:20:30 | Run Step | | .github/workflows/neg_poisonable_step1.yml:17:17:17:60 | steps.comment-branch.outputs.head_sha | .github/workflows/neg_poisonable_step1.yml:14:9:19:6 | Uses Step | | .github/workflows/neg_poisonable_step2.yml:13:9:16:6 | Uses Step | .github/workflows/neg_poisonable_step2.yml:16:9:17:54 | Run Step | -| .github/workflows/poisonable_step1.yml:10:9:12:6 | Uses Step: comment-branch | .github/workflows/poisonable_step1.yml:12:9:15:6 | Uses Step | -| .github/workflows/poisonable_step1.yml:12:9:15:6 | Uses Step | .github/workflows/poisonable_step1.yml:15:9:17:2 | Run Step | -| .github/workflows/poisonable_step1.yml:14:17:14:60 | steps.comment-branch.outputs.head_sha | .github/workflows/poisonable_step1.yml:12:9:15:6 | Uses Step | -| .github/workflows/poisonable_step1.yml:21:9:23:6 | Uses Step: comment-branch | .github/workflows/poisonable_step1.yml:23:9:26:6 | Uses Step | -| .github/workflows/poisonable_step1.yml:23:9:26:6 | Uses Step | .github/workflows/poisonable_step1.yml:26:9:28:2 | Uses Step | -| .github/workflows/poisonable_step1.yml:25:17:25:60 | steps.comment-branch.outputs.head_sha | .github/workflows/poisonable_step1.yml:23:9:26:6 | Uses Step | -| .github/workflows/poisonable_step1.yml:32:9:34:6 | Uses Step: comment-branch | .github/workflows/poisonable_step1.yml:34:9:37:6 | Uses Step | -| .github/workflows/poisonable_step1.yml:34:9:37:6 | Uses Step | .github/workflows/poisonable_step1.yml:37:9:37:75 | Run Step | -| .github/workflows/poisonable_step1.yml:36:17:36:60 | steps.comment-branch.outputs.head_sha | .github/workflows/poisonable_step1.yml:34:9:37:6 | Uses Step | +| .github/workflows/poisonable_step1.yml:12:9:14:6 | Uses Step: comment-branch | .github/workflows/poisonable_step1.yml:14:9:17:6 | Uses Step | +| .github/workflows/poisonable_step1.yml:14:9:17:6 | Uses Step | .github/workflows/poisonable_step1.yml:17:9:19:2 | Run Step | +| .github/workflows/poisonable_step1.yml:16:17:16:60 | steps.comment-branch.outputs.head_sha | .github/workflows/poisonable_step1.yml:14:9:17:6 | Uses Step | +| .github/workflows/poisonable_step1.yml:24:9:26:6 | Uses Step: comment-branch | .github/workflows/poisonable_step1.yml:26:9:29:6 | Uses Step | +| .github/workflows/poisonable_step1.yml:26:9:29:6 | Uses Step | .github/workflows/poisonable_step1.yml:29:9:31:2 | Uses Step | +| .github/workflows/poisonable_step1.yml:28:17:28:60 | steps.comment-branch.outputs.head_sha | .github/workflows/poisonable_step1.yml:26:9:29:6 | Uses Step | +| .github/workflows/poisonable_step1.yml:36:9:38:6 | Uses Step: comment-branch | .github/workflows/poisonable_step1.yml:38:9:41:6 | Uses Step | +| .github/workflows/poisonable_step1.yml:38:9:41:6 | Uses Step | .github/workflows/poisonable_step1.yml:41:9:43:2 | Run Step | +| .github/workflows/poisonable_step1.yml:40:17:40:60 | steps.comment-branch.outputs.head_sha | .github/workflows/poisonable_step1.yml:38:9:41:6 | Uses Step | | .github/workflows/poisonable_step2.yml:15:9:20:6 | Uses Step | .github/workflows/poisonable_step2.yml:20:9:22:6 | Uses Step | | .github/workflows/poisonable_step2.yml:18:17:18:57 | github.event.pull_request.head.ref | .github/workflows/poisonable_step2.yml:15:9:20:6 | Uses Step | | .github/workflows/poisonable_step2.yml:20:9:22:6 | Uses Step | .github/workflows/poisonable_step2.yml:22:9:26:31 | Uses Step | @@ -76,6 +88,9 @@ edges | .github/workflows/poisonable_step5.yml:20:17:20:57 | github.event.pull_request.head.ref | .github/workflows/poisonable_step5.yml:17:9:22:6 | Uses Step | | .github/workflows/poisonable_step5.yml:22:9:24:6 | Uses Step | .github/workflows/poisonable_step5.yml:24:9:28:31 | Uses Step | #select -| .github/workflows/cache_write_capable_reusable_workflow.yml:38:9:40:2 | Run Step | .github/workflows/cache_write_capable_reusable_workflow.yml:33:22:33:43 | inputs.head_sha | .github/workflows/cache_write_capable_reusable_workflow.yml:38:9:40:2 | Run Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code from $@. ($@). | .github/workflows/cache_write_capable_reusable_workflow.yml:33:22:33:43 | inputs.head_sha | inputs.head_sha | .github/workflows/cache_write_capable_reusable_workflow_caller.yml:4:3:4:19 | workflow_dispatch | workflow_dispatch | -| .github/workflows/cache_write_capable_workflow_dispatch.yml:19:9:20:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:15:22:15:68 | fromJSON(steps.pr.outputs.json).head.sha | .github/workflows/cache_write_capable_workflow_dispatch.yml:19:9:20:6 | Run Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code from $@. ($@). | .github/workflows/cache_write_capable_workflow_dispatch.yml:15:22:15:68 | fromJSON(steps.pr.outputs.json).head.sha | fromJSON(steps.pr.outputs.json).head.sha | .github/workflows/cache_write_capable_workflow_dispatch.yml:1:5:1:21 | workflow_dispatch | workflow_dispatch | +| .github/workflows/cache_write_capable_pull_request.yml:43:9:45:2 | Run Step | .github/workflows/cache_write_capable_pull_request.yml:38:17:38:57 | github.event.pull_request.head.ref | .github/workflows/cache_write_capable_pull_request.yml:43:9:45:2 | Run Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code from $@. ($@). | .github/workflows/cache_write_capable_pull_request.yml:38:17:38:57 | github.event.pull_request.head.ref | github.event.pull_request.head.ref | .github/workflows/cache_write_capable_pull_request.yml:2:3:2:14 | pull_request | pull_request | +| .github/workflows/cache_write_capable_pull_request.yml:59:9:61:2 | Run Step | .github/workflows/cache_write_capable_pull_request.yml:54:17:54:57 | github.event.pull_request.head.ref | .github/workflows/cache_write_capable_pull_request.yml:59:9:61:2 | Run Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code from $@. ($@). | .github/workflows/cache_write_capable_pull_request.yml:54:17:54:57 | github.event.pull_request.head.ref | github.event.pull_request.head.ref | .github/workflows/cache_write_capable_pull_request.yml:2:3:2:14 | pull_request | pull_request | +| .github/workflows/cache_write_capable_reusable_workflow.yml:38:9:40:2 | Run Step | .github/workflows/cache_write_capable_reusable_workflow.yml:33:22:33:43 | inputs.head_sha | .github/workflows/cache_write_capable_reusable_workflow.yml:38:9:40:2 | Run Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code from $@. ($@). | .github/workflows/cache_write_capable_reusable_workflow.yml:33:22:33:43 | inputs.head_sha | inputs.head_sha | .github/workflows/cache_write_capable_reusable_workflow_caller.yml:3:3:3:19 | workflow_dispatch | workflow_dispatch | +| .github/workflows/cache_write_capable_workflow_dispatch.yml:43:9:44:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch.yml:39:22:39:68 | fromJSON(steps.pr.outputs.json).head.sha | .github/workflows/cache_write_capable_workflow_dispatch.yml:43:9:44:6 | Run Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code from $@. ($@). | .github/workflows/cache_write_capable_workflow_dispatch.yml:39:22:39:68 | fromJSON(steps.pr.outputs.json).head.sha | fromJSON(steps.pr.outputs.json).head.sha | .github/workflows/cache_write_capable_workflow_dispatch.yml:1:5:1:21 | workflow_dispatch | workflow_dispatch | | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:21:9:22:6 | Run Step | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:16:22:16:56 | github.event.inputs.head_sha | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:21:9:22:6 | Run Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code from $@. ($@). | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:16:22:16:56 | github.event.inputs.head_sha | github.event.inputs.head_sha | .github/workflows/cache_write_capable_workflow_dispatch_validated.yml:2:3:2:19 | workflow_dispatch | workflow_dispatch | +| .github/workflows/poisonable_step1.yml:17:9:19:2 | Run Step | .github/workflows/poisonable_step1.yml:16:17:16:60 | steps.comment-branch.outputs.head_sha | .github/workflows/poisonable_step1.yml:17:9:19:2 | Run Step | Potential cache poisoning in the context of the default branch due to privilege checkout of untrusted code from $@. ($@). | .github/workflows/poisonable_step1.yml:16:17:16:60 | steps.comment-branch.outputs.head_sha | steps.comment-branch.outputs.head_sha | .github/workflows/poisonable_step1.yml:2:3:2:15 | issue_comment | issue_comment | diff --git a/actions/ql/test/query-tests/Security/CWE-349/CacheWriteAccess.expected b/actions/ql/test/query-tests/Security/CWE-349/CacheWriteAccess.expected new file mode 100644 index 000000000000..eb4ac023a47b --- /dev/null +++ b/actions/ql/test/query-tests/Security/CWE-349/CacheWriteAccess.expected @@ -0,0 +1,155 @@ +| cache_mode_nested.yml | inherited | cache_mode_nested.yml | pull_request | false | +| cache_mode_nested.yml | inherited | cache_mode_nested.yml | push | false | +| cache_mode_nested.yml | inherited | cache_mode_nested.yml | workflow_dispatch | true | +| cache_mode_nested.yml | inherited | cache_mode_reusable.yml | pull_request | false | +| cache_mode_nested.yml | inherited | cache_mode_reusable.yml | push | false | +| cache_mode_nested.yml | inherited | cache_write_capable_pull_request.yml | pull_request | true | +| cache_mode_nested.yml | inherited | cache_write_capable_push.yml | push | false | +| cache_mode_nested.yml | inherited | cache_write_capable_reusable_workflow_caller.yml | issue_comment | true | +| cache_mode_nested.yml | inherited | cache_write_capable_reusable_workflow_caller.yml | pull_request_target | true | +| cache_mode_nested.yml | inherited | cache_write_capable_reusable_workflow_caller.yml | push | true | +| cache_mode_nested.yml | inherited | cache_write_capable_reusable_workflow_caller.yml | workflow_dispatch | true | +| cache_mode_nested.yml | inherited | cache_write_capable_reusable_workflow_caller.yml | workflow_run | true | +| cache_mode_nested.yml | inherited | cache_write_capable_workflow_dispatch.yml | workflow_dispatch | false | +| cache_mode_nested.yml | inherited | code_injection1.yml | issue_comment | false | +| cache_mode_nested.yml | inherited | direct_cache1.yml | issue_comment | true | +| cache_mode_nested.yml | inherited | poisonable_step1.yml | issue_comment | false | +| cache_mode_nested.yml | read | cache_mode_nested.yml | pull_request | false | +| cache_mode_nested.yml | read | cache_mode_nested.yml | push | false | +| cache_mode_nested.yml | read | cache_mode_nested.yml | workflow_dispatch | false | +| cache_mode_nested.yml | read | cache_mode_reusable.yml | pull_request | false | +| cache_mode_nested.yml | read | cache_mode_reusable.yml | push | false | +| cache_mode_nested.yml | read | cache_write_capable_pull_request.yml | pull_request | false | +| cache_mode_nested.yml | read | cache_write_capable_push.yml | push | false | +| cache_mode_nested.yml | read | cache_write_capable_reusable_workflow_caller.yml | issue_comment | false | +| cache_mode_nested.yml | read | cache_write_capable_reusable_workflow_caller.yml | pull_request_target | false | +| cache_mode_nested.yml | read | cache_write_capable_reusable_workflow_caller.yml | push | false | +| cache_mode_nested.yml | read | cache_write_capable_reusable_workflow_caller.yml | workflow_dispatch | false | +| cache_mode_nested.yml | read | cache_write_capable_reusable_workflow_caller.yml | workflow_run | false | +| cache_mode_nested.yml | read | cache_write_capable_workflow_dispatch.yml | workflow_dispatch | false | +| cache_mode_nested.yml | read | code_injection1.yml | issue_comment | false | +| cache_mode_nested.yml | read | direct_cache1.yml | issue_comment | false | +| cache_mode_nested.yml | read | poisonable_step1.yml | issue_comment | false | +| cache_mode_nested.yml | write | cache_mode_nested.yml | pull_request | false | +| cache_mode_nested.yml | write | cache_mode_nested.yml | push | false | +| cache_mode_nested.yml | write | cache_mode_nested.yml | workflow_dispatch | true | +| cache_mode_nested.yml | write | cache_mode_reusable.yml | pull_request | false | +| cache_mode_nested.yml | write | cache_mode_reusable.yml | push | false | +| cache_mode_nested.yml | write | cache_write_capable_pull_request.yml | pull_request | true | +| cache_mode_nested.yml | write | cache_write_capable_push.yml | push | false | +| cache_mode_nested.yml | write | cache_write_capable_reusable_workflow_caller.yml | issue_comment | false | +| cache_mode_nested.yml | write | cache_write_capable_reusable_workflow_caller.yml | pull_request_target | false | +| cache_mode_nested.yml | write | cache_write_capable_reusable_workflow_caller.yml | push | false | +| cache_mode_nested.yml | write | cache_write_capable_reusable_workflow_caller.yml | workflow_dispatch | false | +| cache_mode_nested.yml | write | cache_write_capable_reusable_workflow_caller.yml | workflow_run | false | +| cache_mode_nested.yml | write | cache_write_capable_workflow_dispatch.yml | workflow_dispatch | false | +| cache_mode_nested.yml | write | code_injection1.yml | issue_comment | false | +| cache_mode_nested.yml | write | direct_cache1.yml | issue_comment | false | +| cache_mode_nested.yml | write | poisonable_step1.yml | issue_comment | false | +| cache_mode_nested.yml | write-only | cache_mode_nested.yml | pull_request | false | +| cache_mode_nested.yml | write-only | cache_mode_nested.yml | push | false | +| cache_mode_nested.yml | write-only | cache_mode_nested.yml | workflow_dispatch | true | +| cache_mode_nested.yml | write-only | cache_mode_reusable.yml | pull_request | false | +| cache_mode_nested.yml | write-only | cache_mode_reusable.yml | push | false | +| cache_mode_nested.yml | write-only | cache_write_capable_pull_request.yml | pull_request | true | +| cache_mode_nested.yml | write-only | cache_write_capable_push.yml | push | false | +| cache_mode_nested.yml | write-only | cache_write_capable_reusable_workflow_caller.yml | issue_comment | true | +| cache_mode_nested.yml | write-only | cache_write_capable_reusable_workflow_caller.yml | pull_request_target | true | +| cache_mode_nested.yml | write-only | cache_write_capable_reusable_workflow_caller.yml | push | true | +| cache_mode_nested.yml | write-only | cache_write_capable_reusable_workflow_caller.yml | workflow_dispatch | true | +| cache_mode_nested.yml | write-only | cache_write_capable_reusable_workflow_caller.yml | workflow_run | true | +| cache_mode_nested.yml | write-only | cache_write_capable_workflow_dispatch.yml | workflow_dispatch | false | +| cache_mode_nested.yml | write-only | code_injection1.yml | issue_comment | false | +| cache_mode_nested.yml | write-only | direct_cache1.yml | issue_comment | true | +| cache_mode_nested.yml | write-only | poisonable_step1.yml | issue_comment | false | +| cache_mode_reusable.yml | inherited | cache_mode_reusable.yml | pull_request | false | +| cache_mode_reusable.yml | inherited | cache_mode_reusable.yml | push | false | +| cache_mode_reusable.yml | inherited | cache_write_capable_push.yml | push | false | +| cache_mode_reusable.yml | inherited | cache_write_capable_reusable_workflow_caller.yml | issue_comment | true | +| cache_mode_reusable.yml | inherited | cache_write_capable_reusable_workflow_caller.yml | pull_request_target | true | +| cache_mode_reusable.yml | inherited | cache_write_capable_reusable_workflow_caller.yml | push | true | +| cache_mode_reusable.yml | inherited | cache_write_capable_reusable_workflow_caller.yml | workflow_dispatch | true | +| cache_mode_reusable.yml | inherited | cache_write_capable_reusable_workflow_caller.yml | workflow_run | true | +| cache_mode_reusable.yml | inherited | cache_write_capable_workflow_dispatch.yml | workflow_dispatch | false | +| cache_mode_reusable.yml | inherited | code_injection1.yml | issue_comment | false | +| cache_mode_reusable.yml | inherited | direct_cache1.yml | issue_comment | true | +| cache_mode_reusable.yml | inherited | poisonable_step1.yml | issue_comment | false | +| cache_mode_reusable.yml | none | cache_mode_reusable.yml | pull_request | false | +| cache_mode_reusable.yml | none | cache_mode_reusable.yml | push | false | +| cache_mode_reusable.yml | none | cache_write_capable_push.yml | push | false | +| cache_mode_reusable.yml | none | cache_write_capable_reusable_workflow_caller.yml | issue_comment | false | +| cache_mode_reusable.yml | none | cache_write_capable_reusable_workflow_caller.yml | pull_request_target | false | +| cache_mode_reusable.yml | none | cache_write_capable_reusable_workflow_caller.yml | push | false | +| cache_mode_reusable.yml | none | cache_write_capable_reusable_workflow_caller.yml | workflow_dispatch | false | +| cache_mode_reusable.yml | none | cache_write_capable_reusable_workflow_caller.yml | workflow_run | false | +| cache_mode_reusable.yml | none | cache_write_capable_workflow_dispatch.yml | workflow_dispatch | false | +| cache_mode_reusable.yml | none | code_injection1.yml | issue_comment | false | +| cache_mode_reusable.yml | none | direct_cache1.yml | issue_comment | false | +| cache_mode_reusable.yml | none | poisonable_step1.yml | issue_comment | false | +| cache_mode_reusable.yml | read | cache_mode_reusable.yml | pull_request | false | +| cache_mode_reusable.yml | read | cache_mode_reusable.yml | push | false | +| cache_mode_reusable.yml | read | cache_write_capable_push.yml | push | false | +| cache_mode_reusable.yml | read | cache_write_capable_reusable_workflow_caller.yml | issue_comment | false | +| cache_mode_reusable.yml | read | cache_write_capable_reusable_workflow_caller.yml | pull_request_target | false | +| cache_mode_reusable.yml | read | cache_write_capable_reusable_workflow_caller.yml | push | false | +| cache_mode_reusable.yml | read | cache_write_capable_reusable_workflow_caller.yml | workflow_dispatch | false | +| cache_mode_reusable.yml | read | cache_write_capable_reusable_workflow_caller.yml | workflow_run | false | +| cache_mode_reusable.yml | read | cache_write_capable_workflow_dispatch.yml | workflow_dispatch | false | +| cache_mode_reusable.yml | read | code_injection1.yml | issue_comment | false | +| cache_mode_reusable.yml | read | direct_cache1.yml | issue_comment | false | +| cache_mode_reusable.yml | read | poisonable_step1.yml | issue_comment | false | +| cache_mode_reusable.yml | write | cache_mode_reusable.yml | pull_request | false | +| cache_mode_reusable.yml | write | cache_mode_reusable.yml | push | false | +| cache_mode_reusable.yml | write | cache_write_capable_push.yml | push | false | +| cache_mode_reusable.yml | write | cache_write_capable_reusable_workflow_caller.yml | issue_comment | true | +| cache_mode_reusable.yml | write | cache_write_capable_reusable_workflow_caller.yml | pull_request_target | true | +| cache_mode_reusable.yml | write | cache_write_capable_reusable_workflow_caller.yml | push | true | +| cache_mode_reusable.yml | write | cache_write_capable_reusable_workflow_caller.yml | workflow_dispatch | true | +| cache_mode_reusable.yml | write | cache_write_capable_reusable_workflow_caller.yml | workflow_run | true | +| cache_mode_reusable.yml | write | cache_write_capable_workflow_dispatch.yml | workflow_dispatch | false | +| cache_mode_reusable.yml | write | code_injection1.yml | issue_comment | false | +| cache_mode_reusable.yml | write | direct_cache1.yml | issue_comment | true | +| cache_mode_reusable.yml | write | poisonable_step1.yml | issue_comment | false | +| cache_mode_reusable.yml | write-only | cache_mode_reusable.yml | pull_request | false | +| cache_mode_reusable.yml | write-only | cache_mode_reusable.yml | push | false | +| cache_mode_reusable.yml | write-only | cache_write_capable_push.yml | push | false | +| cache_mode_reusable.yml | write-only | cache_write_capable_reusable_workflow_caller.yml | issue_comment | true | +| cache_mode_reusable.yml | write-only | cache_write_capable_reusable_workflow_caller.yml | pull_request_target | true | +| cache_mode_reusable.yml | write-only | cache_write_capable_reusable_workflow_caller.yml | push | true | +| cache_mode_reusable.yml | write-only | cache_write_capable_reusable_workflow_caller.yml | workflow_dispatch | true | +| cache_mode_reusable.yml | write-only | cache_write_capable_reusable_workflow_caller.yml | workflow_run | true | +| cache_mode_reusable.yml | write-only | cache_write_capable_workflow_dispatch.yml | workflow_dispatch | false | +| cache_mode_reusable.yml | write-only | code_injection1.yml | issue_comment | false | +| cache_mode_reusable.yml | write-only | direct_cache1.yml | issue_comment | true | +| cache_mode_reusable.yml | write-only | poisonable_step1.yml | issue_comment | false | +| cache_write_capable_pull_request.yml | default | cache_write_capable_pull_request.yml | pull_request | false | +| cache_write_capable_pull_request.yml | none | cache_write_capable_pull_request.yml | pull_request | false | +| cache_write_capable_pull_request.yml | read | cache_write_capable_pull_request.yml | pull_request | false | +| cache_write_capable_pull_request.yml | write | cache_write_capable_pull_request.yml | pull_request | true | +| cache_write_capable_pull_request.yml | write-only | cache_write_capable_pull_request.yml | pull_request | true | +| cache_write_capable_push.yml | injection | cache_write_capable_push.yml | push | false | +| cache_write_capable_push.yml | none | cache_write_capable_push.yml | push | false | +| cache_write_capable_push.yml | read | cache_write_capable_push.yml | push | false | +| cache_write_capable_push.yml | write | cache_write_capable_push.yml | push | true | +| cache_write_capable_push.yml | write-only | cache_write_capable_push.yml | push | true | +| cache_write_capable_reusable_workflow.yml | code-injection | cache_write_capable_push.yml | push | false | +| cache_write_capable_reusable_workflow.yml | code-injection | cache_write_capable_reusable_workflow_caller.yml | issue_comment | false | +| cache_write_capable_reusable_workflow.yml | code-injection | cache_write_capable_reusable_workflow_caller.yml | pull_request_target | false | +| cache_write_capable_reusable_workflow.yml | code-injection | cache_write_capable_reusable_workflow_caller.yml | push | true | +| cache_write_capable_reusable_workflow.yml | code-injection | cache_write_capable_reusable_workflow_caller.yml | workflow_dispatch | true | +| cache_write_capable_reusable_workflow.yml | code-injection | cache_write_capable_reusable_workflow_caller.yml | workflow_run | false | +| cache_write_capable_reusable_workflow.yml | code-injection | cache_write_capable_workflow_dispatch.yml | workflow_dispatch | false | +| cache_write_capable_reusable_workflow.yml | direct-cache | cache_write_capable_push.yml | push | false | +| cache_write_capable_reusable_workflow.yml | direct-cache | cache_write_capable_reusable_workflow_caller.yml | issue_comment | false | +| cache_write_capable_reusable_workflow.yml | direct-cache | cache_write_capable_reusable_workflow_caller.yml | pull_request_target | false | +| cache_write_capable_reusable_workflow.yml | direct-cache | cache_write_capable_reusable_workflow_caller.yml | push | true | +| cache_write_capable_reusable_workflow.yml | direct-cache | cache_write_capable_reusable_workflow_caller.yml | workflow_dispatch | true | +| cache_write_capable_reusable_workflow.yml | direct-cache | cache_write_capable_reusable_workflow_caller.yml | workflow_run | false | +| cache_write_capable_reusable_workflow.yml | direct-cache | cache_write_capable_workflow_dispatch.yml | workflow_dispatch | false | +| cache_write_capable_reusable_workflow.yml | poisonable-step | cache_write_capable_push.yml | push | false | +| cache_write_capable_reusable_workflow.yml | poisonable-step | cache_write_capable_reusable_workflow_caller.yml | issue_comment | false | +| cache_write_capable_reusable_workflow.yml | poisonable-step | cache_write_capable_reusable_workflow_caller.yml | pull_request_target | false | +| cache_write_capable_reusable_workflow.yml | poisonable-step | cache_write_capable_reusable_workflow_caller.yml | push | true | +| cache_write_capable_reusable_workflow.yml | poisonable-step | cache_write_capable_reusable_workflow_caller.yml | workflow_dispatch | true | +| cache_write_capable_reusable_workflow.yml | poisonable-step | cache_write_capable_reusable_workflow_caller.yml | workflow_run | false | +| cache_write_capable_reusable_workflow.yml | poisonable-step | cache_write_capable_workflow_dispatch.yml | workflow_dispatch | false | diff --git a/actions/ql/test/query-tests/Security/CWE-349/CacheWriteAccess.ql b/actions/ql/test/query-tests/Security/CWE-349/CacheWriteAccess.ql new file mode 100644 index 000000000000..f6bad44015d9 --- /dev/null +++ b/actions/ql/test/query-tests/Security/CWE-349/CacheWriteAccess.ql @@ -0,0 +1,15 @@ +import actions +import codeql.actions.security.CachePoisoningQuery + +from LocalJob job, Event event, boolean canWrite +where + job.getWorkflow().getLocation().getFile().getBaseName() = + [ + "cache_mode_reusable.yml", "cache_mode_nested.yml", + "cache_write_capable_reusable_workflow.yml", "cache_write_capable_push.yml", + "cache_write_capable_pull_request.yml" + ] and + job.getATriggerEvent() = event and + (if hasDefaultBranchCacheWriteAccess(job, event) then canWrite = true else canWrite = false) +select job.getWorkflow().getLocation().getFile().getBaseName(), job.getId(), + event.getLocation().getFile().getBaseName(), event.getName(), canWrite