EnglishРусский Map
Supply Chain Security

Отравление кэша GitHub Actions

title
Отравление кэша GitHub Actions
type
concept
summary
Подконтрольный атакующему job записывает данные в кэш, которые затем восстанавливают продакшен-workflow
tags
security, github-actions, ci-cd
created
2026-05-12
updated
2026-05-12
lang
ru
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 устроены границы видимости и аутентификация кэша.

Две неочевидные особенности механики

  1. Сохранение кэша на шаге post-job в actions/cache@v… обходит блок permissions: у workflow. Шаг кэширования аутентифицируется через внутренний токен, выдаваемый runner'ом сервису кэша. Это не GITHUB_TOKEN самого workflow. Настройка permissions: contents: read не блокирует запись в кэш.

  2. Область видимости кэша привязана к репозиторию и разделяется между всеми триггерами. Запуск 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.

См. также