← All posts
5 min readThe Cachely team

Cache poisoning and CVE-2025-36852: why read-only tokens matter

A shared build cache is a shortcut around work you have already done. It is also, if you are not careful, a way for an attacker to hand every machine on your team a poisoned artifact. That is the shape of CVE-2025-36852 - the cache-poisoning class nicknamed CREEP - and the fix is simpler than it sounds.

What a remote cache actually trusts

Nx hashes a task's inputs - source files, dependencies, environment, the command itself - into a key, then stores the task's output under that key. The next machine that computes the same key downloads the output instead of rebuilding. The whole model rests on one assumption: whatever is stored under a key is what that key would have produced.

Break that assumption and the cache stops being a speed-up and becomes a distribution channel. If an attacker can write an arbitrary artifact under a key your trusted builds will later read, they have injected output into your pipeline without touching your source - and every cache hit downstream spreads it.

For the broader correctness model behind cache keys and determinism, see what makes a good build cache.

How the poisoning happens

The dangerous path is writes from untrusted builds. Pull requests and forks run attacker-influenced code, yet teams often hand those builds the same read-write cache token as their protected branches. So a malicious PR computes a key that a future main build will also compute, uploads a tampered artifact under it, and waits. When main builds and hits that key, it pulls the poisoned output and treats it as its own.

That is CVE-2025-36852 in one sentence: a low-privilege build writing a cache entry that a high-privilege build later trusts.

The fix: split read from write

The defense is least privilege at the token level. Builds you do not trust should be able to read the cache (so PRs still get fast feedback) but never write to it. Only trusted builds - your protected branches - get read-write tokens and are allowed to populate the cache.

  • Read-only tokens for pull requests and forks. They benefit from existing cache entries and cannot create new ones, so a malicious PR has nothing to poison.
  • Read-write tokens only for protected branches. The builds that are allowed to define "what this key produces" are the only ones that can.
  • One token per actor. A token per CI pipeline, repo, or developer means you can revoke any one of them without breaking the rest.

How Cachely handles it

Cachely builds these controls into the cache itself rather than leaving them to convention:

  • Read-only vs read-write scopes. A read-only token can serve hits but is rejected with a 403 on upload - so a fork or PR build physically cannot write to the cache.
  • Content-addressed, immutable artifacts. An existing key cannot be overwritten; a write to an occupied key returns 409. A known-good build can never be silently replaced.
  • Authenticated, workspace-scoped requests. Every request is authenticated before any storage is touched, and a workspace can never read another's artifacts.

What to do today

Whether or not you use Cachely, audit which builds hold a write-capable cache token. If your PR and fork builds can write to the same cache your release builds read, close that gap first - it is the single change that neutralizes CREEP. The docs walk through issuing read-only and read-write tokens per actor.

A cache that is safe by default

Read-only tokens, immutable artifacts, per-workspace isolation. Free tier - no credit card.