Skip to content

Back to the catalog

GitHub Actions Adds cache-mode to Limit Cache Poisoning Risk
socket.devarticle

GitHub Actions Adds cache-mode to Limit Cache Poisoning Risk

GitHub added cache-mode to Actions, a least-privilege control (read/write/write-only/none) that limits which workflows can restore or save entries in the shared Actions cache, targeting the cache poisoning technique used to compromise Ultralytics (PyPI) and the TanStack npm packages via a pull_request_target pwn request. Job-level settings override workflow-level ones, the mode propagates through reusable workflows, and GitHub warns when write access is granted to low-trust events; write-only was added after feedback from researcher Adnan Khan. The article notes build provenance (SLSA/Sigstore) doesn't catch a poisoned cache since it only attests to build origin, not input integrity, and recommends cache-mode: none for AI agent workflows exposed to prompt injection, referencing the Cline compromise.

TL;DR GitHub Actions now offers a per-job cache-mode (read/write/write-only/none) to curb the cache-poisoning technique behind the TanStack npm and Ultralytics PyPI compromises.
Document

What it is

cache-mode is a GitHub Actions setting that applies least-privilege access to the shared Actions cache, per workflow or per job. The summary names the four values; the page pins down their defaults and enforcement, and situates them against a baseline GitHub already shipped.

How it works

The four values and their defaults:

  • read — restores caches, blocks saves. Default for low-trust events such as pull_request_target.
  • write — restores and saves. Default for trusted events such as push.
  • write-only — saves but blocks restores, so a trusted branch can populate a cache without ever restoring one (the case Adnan Khan pushed for).
  • none — blocks all cache access.

The cache service itself enforces the mode. Job-level overrides workflow-level. The mode carries through reusable workflows so a called workflow cannot receive more cache access than its caller granted. Declaring write or write-only for a low-trust event reintroduces poisoning risk, so GitHub emits a warning annotation whenever a declared mode grants write access.

Preconditions / Affected

Cache poisoning matters when an attacker gains write access to a cache key a trusted workflow later restores — the entry runs with that workflow's permissions and secrets. In the TanStack attack (May 2026), chained with a pull_request_target pwn request, this yielded 84 malicious versions across 42 @tanstack/* packages. Build provenance does not catch this: SLSA/Sigstore attest that the build ran where and when declared, not that its cached inputs were untampered.

What to do

Grant each workflow or job only the cache access it needs. Set cache-mode: none for AI agent workflows that act on issue or PR text (the exposure behind the Cline compromise, where a prompt injection in a GitHub issue led to a suspected cache poisoning attack) — this removes the cache from that environment entirely.

Caveats

  • This layers on defaults GitHub already shipped: read-only cache defaults for most untrusted triggers landed in June 2026, so workflows that never configured caching were already blocked from saving through those events. cache-mode adds per-job control, the none option, service-side enforcement, and the reusable-workflow cap on top.
  • It does not close other Actions risks. Workflows that check out untrusted code, run package lifecycle scripts, or mint OIDC tokens inside the runner remain exposed through the same paths as the TanStack attack.

Organisations named here

  • Adnan Khan
  • Cline compromise
  • TanStack attack

Technologies named here

  • cache-mode
  • Ultralytics
  • SLSA provenance
  • @tanstack/* packages
  • GitHub Actions
  • Sigstore
  • Cline
  • npm
  • PyPI
  • tanstack
  • SLSA

Techniques named here

  • Cache poisoning
  • Pwn Request
  • Prompt Injection

Threat actors named here

  • Cline compromise attacker