В файле чисто. А что говорит сервис, выдавший ключ?
Вы удалили секрет. А доступ отозвали? GhostAction делает этот вопрос срочным: скомпрометированная учётная запись сопровождающего позволяет добавить workflow, который ищет секреты и в старых коммитах, и в текущих файлах. Чистое рабочее дерево не показывает, действует ли найденный ключ. Это решает сервис, который его выдал.
Кампания октября 2026 года — отправная точка. Полезный результат разбора — способ закрыть инцидент на основании доказательств. Я разделяю три вопроса: где сохранился секрет, принимает ли его выдавший сервис и может ли атакующий внедрить ещё один workflow. Анализ основан на открытых исследованиях и документации GitHub; код кампании здесь не запускался. [1][2]
История Git становится входными данными для захваченного runner
StepSecurity описывает вредоносный security-audit.yml, добавленный напрямую от имени скомпрометированного сопровождающего. Checkout получает полную историю, затем скрипт просматривает изменения через git log -p --all. Он также пытается собрать значения настроенных секретов Actions, подставляя имена, найденные в существующих workflow. Имена указывают, что запросить; атакующему нужны значения. [1]
Посмотрите на условную историю ниже. Коммит B убирает токен из последней версии файла, но коммит A продолжает его хранить. Поиск по истории может восстановить прежний текст. При этом сбор ограничен полученными refs, шаблонами поиска и лимитами вывода в payload. Возможность поиска не доказывает, что все исторические секреты были успешно извлечены.
Illustrative repository history; no real credential
A: config.env contains API_TOKEN=DEMO_NOT_A_REAL_TOKEN
B: config.env no longer contains API_TOKEN
Current file: absent
Earlier commit: present
Issuer status: a separate questionМожет ли восстановленный секрет ещё пройти аутентификацию?
Возможность найти секрет и его действительность — независимые свойства. Старый токен может оставаться видимым после отзыва, уже не предоставляя доступа. Он может исчезнуть из вашего репозитория, но продолжать работать из чужой копии. GitHub рекомендует сначала отозвать или заменить раскрытые учётные данные; переписывание истории решает вопрос оставшихся копий и требует координации для клонов, форков и ссылок. [2]
Это меняет трактовку статистики очистки. Число репозиториев со старым текстом секрета измеряет публичные остатки, а не число ключей, которые сервис ещё принимает. Меняется и критерий приёмки: заменить переменную CI недостаточно, пока предыдущий ключ не отключён у выдавшего его сервиса. Фиксируйте это отдельно от успешной сборки с новым ключом.
Old credential accepted?
Yes No
Secret text recoverable Access remains Residual disclosure
Secret text no longer found Access may remain Both checks needed
A repository scan cannot answer the issuer-status column.Какой контроль действительно остановит следующий workflow?
Требование ревью помогает только там, где оно обязательно. Настройка GitHub для одобрения workflow из публичных форков относится именно к такому пути внесения изменений. Это не универсальный шлюз для прямого push от сопровождающего. StepSecurity сообщает, что approval gate остановил отдельные наблюдавшиеся запуски, но это не доказывает, что настройка для форков защищает любой репозиторий от этой кампании. [1][3]
Для защищённой ветки требуйте pull request и ревью нужного владельца кода, затем проверяйте право обхода правила. Запись .github/workflows/ в CODEOWNERS задаёт владельца; правило ветки делает его ревью обязательным. Защитите и сам CODEOWNERS, изучите другие ветки, где push-workflow получает секреты. Защита только main не описывает все пути исполнения workflow. [4][5]
Токен репозитория только для чтения не исключает кражу секретов
Сокращать права GITHUB_TOKEN полезно, но это не отзывает облачный ключ, отдельно переданный задаче. Наблюдавшийся workflow мог работать с правом чтения содержимого репозитория и одновременно собирать другие учётные данные. Поэтому проверка прав должна учитывать и встроенный токен, и каждый отдельный секрет, доступный при выполнении. [1]
Я разбираю конкретную цепочку: кто меняет workflow, какое событие его запускает, какую идентичность и секреты получает задача и куда она может обращаться. Для задачи публикации важно, может ли автор workflow превратить её в чтение ключей. Запрет записи в репозиторий закрывает лишь часть цепочки. Это вопрос архитектуры, а не утверждение, что любой runner раскрывал все секреты.
Ограничьте доступ до косметической очистки истории
Сохраните ревизию workflow, идентификаторы запусков и нужные записи аудита, пока очистка не уничтожила контекст. Остановите вредоносное выполнение и отзовите скомпрометированные учётные данные сопровождающего. Затем составьте перечень текущих секретов задач и исторических ключей в области воздействия, назначьте владельцев и подтвердите отзыв у выдавших сервисов. Неизвестный статус оставляйте нерешённым, не объявляйте репозиторий чистым. [1][2]
Новый ключ должен попасть в доверенный путь выполнения. Иначе тот же захваченный workflow прочитает его при следующем запуске. Очистка истории требует отдельной координации и не возвращает копии, уже полученные атакующим. Если ключ позволял создавать сессии или другие учётные данные, проверьте и их: отключение исходного секрета само по себе не доказывает отсутствие отдельного доступа.
Запросите запись о закрытии, а не чистый скриншот
Документ о завершении исправления должен позволять другому инженеру отличить факты от предположений. Для каждого раскрытого ключа сохраните несекретный идентификатор, владельца, выдавшую систему, окно воздействия, доказательство отзыва и статус внедрения замены. Отдельно укажите, как ограничены исходная скомпрометированная идентичность и путь запуска workflow. Форма ниже — предложенный документ проверки, а не журнал этого инцидента.
Вопрос заказчика становится точным: может ли команда показать, что ранее раскрытые учётные данные больше не принимаются, а замена не утечёт тем же путём? Именно такие доказательства я запрашиваю при проверке исправлений. Они позволяют закрыть доступ, ради устранения которого была создана задача, а не только саму задачу.
Credential ID / owner / issuer: ____________________
Exposure scope and evidence: _______________________
Old credential disabled at issuer: _________________
Replacement deployed through trusted workflow: _____
Compromised maintainer access contained: ____________
Other credentials or sessions investigated: _________
Remaining copies / uncertainty / responsible owner: _Источники
Нужна такая проверка для вашего продукта?
Помощь с исправлениями и проверкаХотите сами научиться находить такие уязвимости?
Курс аудита безопасности кода, индивидуально