Отравление кэша GitHub Actions
- title
- Отравление кэша GitHub Actions
- type
- concept
- summary
- Подконтрольный атакующему job записывает данные в кэш, которые затем восстанавливают продакшен-workflow
- parent
- supply-chain-security
- tags
- security, github-actions, ci-cd
- created
- 2026-05-12
- updated
- 2026-05-12
- lang
- ru
- translation_of
- github-actions-cache-poisoning
- source_updated
- 2026-05-12
- translated
- 2026-09-01
- translator
- lllm/antigravity/gemini-3.7-flash-medium
В GitHub Actions есть кэш сборки уровня репозитория (actions/cache). Любой запуск workflow в репозитории может читать и записывать записи в одно и то же пространство имён кэша, включая запуски по PR из форков. Если атакующий может выполнить код внутри любого workflow в репозитории, он может записать данные, которые позже будут восстановлены продакшен-workflow на ветке main.
Этот вектор подробно описал Аднан Хан (Adnan Khan) в мае 2024 года ("The Monsters in Your Build Cache"). Это не баг TanStack, не специфичная для TanStack проблема и не недавно исправленная уязвимость. Это архитектурное свойство того, как в Actions устроены границы видимости и аутентификация кэша.
Две неочевидные особенности механики
-
Сохранение кэша на шаге post-job в
actions/cache@v…обходит блокpermissions:у workflow. Шаг кэширования аутентифицируется через внутренний токен, выдаваемый runner'ом сервису кэша. Это неGITHUB_TOKENсамого workflow. Настройкаpermissions: contents: readне блокирует запись в кэш. -
Область видимости кэша привязана к репозиторию и разделяется между всеми триггерами. Запуск
pull_request_targetвыполняется в контексте базового репозитория и пишет в его область кэша. Запускpull_requestиз форка ограничен, но всё равно читает и пишет по общим правилам. А шагpushв веткуmainищет ключи в том же пространстве имён, которое мог наполнить любой из этих PR-запусков.
Комбинация: получить возможность исполнения кода в workflow, где запускается post-шаг actions/cache (проще всего через pwn-request-pattern), записать вредоносные данные под ключом, который позже вычислит продакшен-workflow, и подождать. При следующем merge в main запустится релизный workflow, и шаг восстановления actions/cache подтянет эту запись. Вредоносные файлы окажутся на диске runner'а.
Что именно записывает атакующий
Канонический пример - инцидент с TanStack. Вредоносный скрипт vite_setup.mjs выполнил запись в директорию pnpm-store и сохранил её под ключом Linux-pnpm-store-${hashFiles('**/pnpm-lock.yaml')}. Именно этот ключ легитимный workflow release.yml должен был вычислить на шаге Setup Tools при следующем push в main. Отравление было направлено на конкретный будущий ключ кэша конкретного будущего workflow.
После восстановления отравленный pnpm-store содержал исполняемые файлы, которые вызывались на следующих шагах сборки. Затем эти бинарники делали то, что код на runner'е может делать всегда, - но уже с правами id-token: write релизного workflow, что привело к ci-runner-token-extraction.
Способы защиты
Лучший: не делить область кэша между разными уровнями доверия.
- Релизный workflow должен скачивать зависимости заново либо восстанавливать их только из того кэша, в который не может писать workflow, запускаемый по PR. В Actions нет встроенной "доверенной области кэша"; рабочие варианты - (a) полностью отключить кэш при релизе или (b) использовать отдельный префикс ключа кэша, совпадение с которым для PR-workflow исключено структурно.
Хороший: безопасно разделять ключи по областям.
- Добавлять
${{ github.ref }}или компонент с именем ветки в ключи кэша для релизных workflow, чтобы запуски из PR не могли их угадать или вызвать коллизию. У PR, запущенного наrefs/pull/.../merge, и push, запущенного наrefs/heads/main, хэши ключей будут отличаться.
Практичный: проверить вызовы кэша во всех pull_request_target workflow.
- Всё, что попадает под pwn-request-pattern плюс использует
actions/cache(напрямую или транзитивно, через составные action вродеactions/setup-node), является вектором для отравления. Нужно либо убрать связкуpull_request_target+ checkout форка, либо убрать кэш из этого workflow.
Инструменты.
zizmorумеет находить этот паттерн.- Cache API в GitHub Actions позволяет очистить отравленный кэш (команда TanStack сделала это для всех репозиториев
TanStack/*в рамках ликвидации последствий), но это мера реагирования, а не предотвращения.
Почему "переключить permissions в read-only" не помогает
Наивное исправление - "выставить permissions: contents: read для ненадёжного job'а и считать его изолированным" - не работает, потому что запись в кэш не использует эти разрешения. Это самое распространённое ложное чувство безопасности в реальных workflow.
См. также
- pwn-request-pattern - наиболее частый способ получить исполнение кода для отравления
- ci-runner-token-extraction - что делают отравленные бинарники после восстановления кэша на привилегированном runner'е
- supply-chain-security - концепция в широком смысле
- tanstack-npm-supply-chain-postmortem - цепочка атаки
- Разбор Аднана Хана: https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/