Паттерн Pwn Request
- title
- Паттерн Pwn Request
- type
- concept
- summary
- GitHub pull_request_target исполняет код из форка в контексте прав основного репозитория
- parent
- supply-chain-security
- tags
- security, github-actions, ci-cd
- created
- 2026-05-12
- updated
- 2026-05-12
- lang
- ru
- translation_of
- pwn-request-pattern
- source_updated
- 2026-05-12
- translated
- 2026-09-01
- translator
- lllm/antigravity/gemini-3.7-flash-medium
В GitHub Actions есть два триггера для PR. pull_request запускается на коде форка с областью видимости секретов форка (по умолчанию пустой) и требует одобрения для новых участников. pull_request_target запускается в контексте целевого репозитория с его секретами и не требует такого одобрения, поскольку изначально проектировался для доверенных операций (навешивание меток, комментарии, отправка превью развёртывания) над недоверенными PR.
Паттерн "pwn request" - это workflow с триггером pull_request_target, который где-то внутри исполняет код из форка. Классический вид:
on:
pull_request_target:
jobs:
build:
steps:
- uses: actions/checkout@v…
with:
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- run: <build / test / lint>
Строка с checkout - загрузка ссылки merge из PR (или ветки форка) - подтягивает код из форка. Шаг run запускает его на исполнение. Поскольку триггером служит pull_request_target, workflow получает права GITHUB_TOKEN целевого репозитория, его секреты и (нередко) id-token: write. Неизвестный человек только что получил выполнение произвольного кода в доверенном контексте вашего репозитория.
GitHub Security Lab назвала это паттерном "pwn request" в 2021 году, и название прижилось.
Что делает его ещё опаснее
Масштаб проблемы часто недооценивают из-за двух неочевидных фактов:
- Блок
permissions:контролирует не всё. Сохранение кэша вactions/cache@v…по завершении шага использует внутренний токен runner'а, а неGITHUB_TOKENиз workflow. Конфигурацияpermissions: contents: readне запрещает запись в кэш. См. github-actions-cache-poisoning. - Разделение доверия внутри одного workflow даёт утечку. Если выставить
permissions: contents: readи рассчитывать, что "сборка не доверена, а отправка комментария доверена", получается разделение намерений, а не разделение исполнения. Сборка всё равно идёт в окружении целевого репозитория, может прочитать всё содержимое диска в образе runner'а и записать данные в кэш.
Защита с помощью двух workflow
Рекомендуемое решение - структурное. Разделение на два workflow:
-
Workflow с
pull_request_target, делающий только то, для чегоpull_request_targetзадумывался: метки, комментарии, проверки. Он не делает checkout кода из форка. Он не запускает код из форка. Если ему нужно инициировать сборку, он вызываетworkflow_dispatchтолько после явного ручного подтверждения человеком. -
Workflow с триггером
pull_request(илиworkflow_dispatch), который действительно собирает проект. Он запускается на коде форка без секретов репозитория и требует подтверждения для первого запуска от нового участника. Всё, что требует настоящих секретов для валидации, выполняется только после подтверждения человеком.
Инцидент с TanStack произошёл именно из-за попытки совместить оба подхода в одном файле с разделением доверия внутри него. По замыслу это было верно, но механизм оказался недостаточным. Как только внутри контекста pull_request_target выполнился actions/checkout ссылки merge из форка, последствия стали неизбежными.
Обнаружение
zizmorвыполняет линтинг YAML-файлов GitHub Actions и помечает связкуpull_request_target+actions/checkoutветки форка как срабатывание "pwn request".- Материал от GitHub Security Lab о pwn request: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
- Разбор более широкого вектора с отравлением кэша от Adnan Khan: https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/
Известные инциденты
- 2026-05-11 - TanStack.
bundle-size.ymlзапускался поpull_request_targetи собирал код форка. Кэш был отравлен. Полная цепочка описана в tanstack-npm-supply-chain-postmortem. - 2025-03 - tj-actions/changed-files. Немного другой вектор (force-push тега в незафиксированном action), но тот же итог: выполнение кода в релизных workflow зависимых проектов, кража токенов OIDC.
См. также
- supply-chain-security
- github-actions-cache-poisoning - усиление через запись в кэш, превращающее один pwn request в долгосрочное закрепление в системе
- ci-runner-token-extraction - что делают атакующие после получения доступа к исполнению кода
- tanstack-npm-supply-chain-postmortem - вся цепочка на практике