Documentation

Cache tokens and access control

A cache token is the only credential in the system. It identifies a workspace, carries a scope, and is checked before any storage is touched - which makes the read-only scope the mechanism that keeps untrusted builds from poisoning your cache.

Read-only and read-write

Every token is one or the other, chosen at creation:

  • Read-write - can download and upload. Give these to trusted builds only: protected branches and CI pipelines you control.
  • Read-only - can download, never upload. Every write is rejected with a 403 at the API, before storage. Give these to pull-request builds, fork builds, developer machines, and any runner shared across repositories.

The scope is enforced server-side. That distinction matters: a build tool flag that disables uploads (Gradle's isPush, Bazel's --remote_upload_local_results) is guidance a later config line or a command-line argument can override. A read-only token is a boundary nothing on the build machine can talk its way past.

Why this closes CVE-2025-36852

CVE-2025-36852 ("CREEP") is the cache-poisoning class of attack: a build that can write to a shared cache can publish an artifact under a key that a trusted build will later download and execute. It is what led Nx to deprecate its official self-hosted cache packages, because a shared bucket credential cannot express "read but do not write".

Two properties close it here. Untrusted builds get a read-only token, so they cannot write at all. And managed artifacts are immutable: an existing cache key cannot be overwritten - the API returns 409 - so even a trusted writer cannot silently replace a known good build. Background on the vulnerability is in the CREEP write-up.

How many tokens to issue

One per pipeline, repository, or developer. A token is scoped to a single workspace and carries no other identity, so the only way to revoke access for one consumer without breaking the rest is to have given it its own token in the first place. Name them for where they run - github-actions-prod, alice-laptop - because the name is what you will be reading when you decide what to revoke.

Storage, rotation, and revocation

  • Tokens are stored hashed (SHA-256). The raw value is shown once at creation and never again - if it is lost, issue a new one and revoke the old.
  • Every cache request is authenticated. An unknown or revoked token is rejected with a 401 before any storage is touched.
  • Workspaces are isolated per owner; one workspace can never read another's artifacts.
  • Revoke a writer immediately if you suspect a poisoned entry, then rotate the remaining writers.

Keep the raw value out of your repository. In CI it belongs in the secret store; locally, in your shell profile or a gitignored file:

# .gitignore
user.bazelrc
.env.local

Next

CI setup shows how to select between a read-write and a read-only token per event, and the security page covers Cachely's posture more broadly.

Lock down your cache
Read-only tokens for untrusted builds, immutable artifacts for everyone.
Start freeSee pricing