Skip to content

Actions cache-poisoning query ignores workflow/job cache-mode #23006

Open

Description

@richlander

Actions cache-poisoning query ignores workflow/job cache-mode

Description of the false positive

actions/cache-poisoning/poisonable-step reports the same finding whether a job
has read-only or writable cache authority. GitHub documents cache-mode: read
as backend-enforced through scoped cache tokens, preventing cache saves.

I reproduced this with CodeQL CLI 2.27.4, codeql/actions-queries 0.6.39,
and codeql/actions-all 0.7.1 from the checksum-verified Linux bundle.
The query pack's build SHA is
1be292a1e54696ca543b4ad0f9b4537b9687caa7.

Code samples

This is synthetic source. It was extracted and analyzed locally; no workflow,
checkout, or npm command was executed.

name: Cache-mode reproduction
on:
  workflow_dispatch:
    inputs:
      source_sha:
        description: Commit to inspect
        required: true
        type: string
permissions:
  contents: read
cache-mode: read
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
        with:
          ref: ${{ inputs.source_sha }}
      - run: npm ci

Four separate source roots differed only in their cache policy:

Variant Workflow cache-mode Job cache-mode Expected findings Observed findings
read read absent 0 1
default absent absent 1 1
job-read absent read 0 1
job-write read write 1 1

The write-capable cases are positive controls, assuming the query's threat
model for manually supplied source. A fix must account for job overrides,
rather than suppress every workflow that contains a read-only declaration.

Each result reports:

Potential cache poisoning in the context of the default branch due to
privilege checkout of untrusted code from inputs.source_sha.
(workflow_dispatch).

Commands

Run these for each variant, using separate database and output paths:

codeql database create db-read \
  --language=actions --build-mode=none --source-root=fixtures/read
codeql database analyze db-read \
  'codeql/actions-queries@0.6.39:Security/CWE-349/CachePoisoningViaPoisonableStep.ql' \
  --threads=2 --ram=4096 --format=sarif-latest --output=read.sarif
jq '[.runs[].results[]? |
  select(.ruleId == "actions/cache-poisoning/poisonable-step")] | length' \
  read.sarif

Relevant model and platform contract

The cache-write predicate considers the triggering event, but does not inspect
explicit workflow/job cache policy:

private string defaultBranchCacheWriteEvent() {
result =
[
"push", "workflow_dispatch", "repository_dispatch", "delete", "registry_package",
"page_build", "schedule"
]
}
private predicate eventHasDefaultBranchCacheWriteAccess(Event event) {
runsOnDefaultBranch(event) and event.getName() = defaultBranchCacheWriteEvent()
}
/**
* Holds if `job` can write to the cache scope of the default branch for `event`.
* Reusable workflow jobs inherit their caller's trigger event.
*/
predicate hasDefaultBranchCacheWriteAccess(LocalJob job, Event event) {
job.getATriggerEvent() = event and
eventHasDefaultBranchCacheWriteAccess(event)
}

GitHub's documented policy:

https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching#controlling-cache-access-with-cache-mode

https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idcache-mode

Reusable-workflow caller limits/inheritance should also be covered by a model
fix, but those cases were not included in this minimal executed reproduction.
This report verifies static query behavior; it does not independently test
cache-token enforcement in the backend.

Authored by GitHub Copilot (GPT-6.1 Sol).

Activity

  1. sam-robson commented on Nov 4, 2026

    @sam-robson
    Contributor

    Thanks for the report. Work is ongoing to add cache-mode support to the Actions cache-poisoning queries in #22613.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ActionsAnalysis of GitHub Actions

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions