# CI cache poisoning and token leakage

Client-controlled cache keys and unrestricted archive extraction letting untrusted code poison privileged CI workflows

_Area: Software supply chain and CI/CD. Seen in 4 disclosed reports; 3 confirmed bounties, median $1,000, range $500-$2,000; latest 2026-05-12. Updated 2026-10-23._

## How it works
CI caches are treated as trusted storage, but the key and archive contents are attacker-influenced. In GitHub Actions the cache key/version are computed client-side and not validated server-side, and the toolkit's restore step extracts the archive without confining it to the configured paths [E1](https://bugbountylibrary.com/r/blog%3Aadnanthekhan-com-2024-05-06-the-monsters-in-your-build-cache-github-actions-cache-poisoning.md). So any code execution on the default branch can write a cache entry that a different, privileged workflow later restores, overwriting arbitrary files in its working directory. The same trust gap appears when a user-controlled cache key is concatenated into a shared backend URL path, letting one tenant reach another's cache namespace [E3](https://bugbountylibrary.com/r/h1%3A301432.md). The broken assumption: that a cache entry was produced by the same trust context that consumes it.

## Where it shows up
- Workflows that restore caches and also hold `id-token: write`, release credentials, or publish rights [E2](https://bugbountylibrary.com/r/blog%3Astepsecurity-io-blog-mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem.md)
- `pull_request_target` (or similar) workflows that run untrusted code yet share cache scope with release jobs [E2](https://bugbountylibrary.com/r/blog%3Astepsecurity-io-blog-mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem.md)
- Cache keys built from user-controlled fields (branch names, PR metadata, matrix values) [E1](https://bugbountylibrary.com/r/blog%3Aadnanthekhan-com-2024-05-06-the-monsters-in-your-build-cache-github-actions-cache-poisoning.md)[E3](https://bugbountylibrary.com/r/h1%3A301432.md)
- Cache `key` interpolated into a URL path on a shared cache/artifact server [E3](https://bugbountylibrary.com/r/h1%3A301432.md)
- Restore steps that extract archives without path restriction [E1](https://bugbountylibrary.com/r/blog%3Aadnanthekhan-com-2024-05-06-the-monsters-in-your-build-cache-github-actions-cache-poisoning.md)
- Release pipelines relying on SLSA provenance as their only integrity signal [E2](https://bugbountylibrary.com/r/blog%3Astepsecurity-io-blog-mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem.md)

## How to review
The invariant: a cache entry must only be consumable by a workflow with equal or lower trust than the one that wrote it, and extraction must stay within declared paths. Check that privileged/release workflows do not share cache scope with PR-triggered or otherwise untrusted jobs; that cache keys cannot be chosen by an attacker to collide with a privileged job's key; that restore confines extraction to the configured directories; and that cache keys are sanitized before use in any URL path [E3](https://bugbountylibrary.com/r/h1%3A301432.md). For release jobs, confirm the OIDC token is not reachable from code paths an attacker can influence [E2](https://bugbountylibrary.com/r/blog%3Astepsecurity-io-blog-mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem.md).

## Variants seen
- Client-keyed cache poisoning overwriting files in privileged workflows [E1](https://bugbountylibrary.com/r/blog%3Aadnanthekhan-com-2024-05-06-the-monsters-in-your-build-cache-github-actions-cache-poisoning.md)
- Cache poisoning chained with a `pull_request_target` Pwn Request to steal OIDC tokens and publish packages with valid provenance [E2](https://bugbountylibrary.com/r/blog%3Astepsecurity-io-blog-mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem.md)
- Path traversal in a shared cache key crossing project namespaces to read and poison other tenants' caches [E3](https://bugbountylibrary.com/r/h1%3A301432.md)

## Why fixes fail
Provenance and signing attest which pipeline built an artifact, not that the pipeline was uncompromised; a poisoned-but-legitimate release still carries valid SLSA provenance [E2](https://bugbountylibrary.com/r/blog%3Astepsecurity-io-blog-mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem.md). Read-only tokens and absent secrets do not make a workflow safe if it can still write caches consumed elsewhere [E1](https://bugbountylibrary.com/r/blog%3Aadnanthekhan-com-2024-05-06-the-monsters-in-your-build-cache-github-actions-cache-poisoning.md).

## Impact and severity
Led to arbitrary file overwrite in privileged workflows [E1](https://bugbountylibrary.com/r/blog%3Aadnanthekhan-com-2024-05-06-the-monsters-in-your-build-cache-github-actions-cache-poisoning.md), cross-tenant cache read/poison and code execution in victim pipelines [E3](https://bugbountylibrary.com/r/h1%3A301432.md), and publishing 84 malicious npm versions with valid provenance [E2](https://bugbountylibrary.com/r/blog%3Astepsecurity-io-blog-mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem.md). Severity rises when cache scope is shared across trust boundaries and release jobs hold ambient OIDC or publish credentials.

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Aadnanthekhan-com-2024-05-06-the-monsters-in-your-build-cache-github-actions-cache-poisoning.md
- E2: https://bugbountylibrary.com/r/blog%3Astepsecurity-io-blog-mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem.md
- E3: https://bugbountylibrary.com/r/h1%3A301432.md
