Последние два года я повторяю командам, с которыми работаю, одну и ту же фразу: «переходите на OIDC и убирайте AWS_ACCESS_KEY_ID из GitHub Secrets». Этот совет по-прежнему актуален. Но после атак в мае 2026 года — сначала на actions-cool/issues-helper, а затем Megalodon с его 5 561 репозиторием-ловушкой — я формулирую его уже иначе.
Проблема не в OIDC. Проблема в том, что OIDC стали воспринимать как финальную точку в вопросах безопасности, хотя он должен быть лишь отправной точкой. Две майские атаки устроены по-разному, но обе эксплуатируют одну и ту же забытую границу доверия — раннер CI/CD.
В случае с actions-cool/issues-helper атакующий подменяет теги action и добивается выполнения подложного коммита, который может читать память процесса Runner.Worker. В случае с Megalodon он напрямую внедряет вредоносные workflow GitHub Actions в публичные репозитории и собирает переменные окружения, файлы с учетными данными, kubeconfig-файлы, SSH-ключи и OIDC-токены.
В обоих случаях вывод один: OIDC позволяет отказаться от статических учетных данных для доступа к облаку, но не делает безопасным код, который выполняется внутри job и получает доступ к временным учетным данным. Эта статья — поправка к тому, как я сам раньше объяснял эту тему. Я не собираюсь отказываться от рекомендации использовать OIDC. Я лишь хочу четко показать, какие меры нужно выстроить вокруг него, чтобы этот подход действительно работал в 2026 году.
Какую проблему на самом деле решает OIDC?
Классический сценарий можно описать одной строкой: секрет с AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY хранится в GitHub Secrets, действует месяцами, вручную передается от одного участника команды другому, забывается в форке и в итоге утекает в тот день, когда сторонний workflow запускается со слишком широкими правами.
OIDC ломает эту схему. GitHub подписывает JWT, который описывает идентичность job: repo, ref, workflow, job, environment. Целевое облако проверяет подпись, применяет свою политику доверия и возвращает временные учетные данные, действующие от 15 минут до часа. На стороне GitHub больше не нужно защищать долгоживущие учетные данные.
Чего OIDC не решает?
Временные учетные данные, полученные через OIDC, не хранятся постоянно в GitHub как секрет, но во время выполнения job они превращаются в данные, которыми можно воспользоваться. Это может быть переменная окружения — например, AWS_ACCESS_KEY_ID или AWS_SESSION_TOKEN, которые aws-actions/configure-aws-credentials экспортирует по умолчанию, — файл с учетными данными, токен, запрошенный одним из шагов, или другой артефакт, доступный из контекста раннера в зависимости от используемого action.
Любой код, который выполняется в этом временном окне на том же раннере, может увидеть эти учетные данные. Вредоносный сторонний action, внедренный workflow, post-install-скрипт npm-зависимости, чтение /proc/<PID>/environ или /proc/<PID>/mem — любой из этих способов позволяет извлечь чувствительные данные.
Именно так работает Megalodon: атакующий напрямую внедряет вредоносный workflow, который собирает переменные окружения раннера, файлы с учетными данными, kubeconfig-файлы и OIDC-токены, запрошенные из job. Облачного токена, действующего 15 минут, вполне достаточно для массовой автоматизированной атаки: у злоумышленника есть инструменты, чтобы быстро вывести его наружу и повторно использовать, пока не истек срок действия.
Поэтому здесь важно избавиться от одной иллюзии: утверждение «OIDC = учетные данные больше нельзя украсть» неверно. OIDC убирает постоянные учетные данные, но не краткоживущие. Украсть можно и те и другие — просто окно риска у них совершенно разное.
Какой сценарий я не предусмотрел?
Представьте аккуратный workflow deploy.yml — таких я написал уже десятки. Разрешения по умолчанию только на чтение. id-token: write задан только для job развертывания. Политика доверия AWS требует repo:my-org/my-repo:ref:refs/heads/main. Action aws-actions/configure-aws-credentials зафиксирован по SHA. На бумаге это именно то, что мы называем защищенным workflow.
jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - uses: actions/checkout@<SHA> - uses: aws-actions/configure-aws-credentials@<SHA> with: role-to-assume: arn:aws:iam::123456789012:role/deploy aws-region: eu-west-3 - run: ./scripts/deploy.sh
Проблема остается незаметной, пока в workflow не появляется посторонний шаг. Если атакующий внедрит такой шаг до или после configure-aws-credentials — через вредоносный коммит в .github/workflows/, сторонний action, скомпрометированный через его тег, или зависимость, которая выполняется внутри ./scripts/deploy.sh, — он получит либо JWT от GitHub до обмена, либо временные учетные данные AWS после него.
В обоих случаях у атакующего есть окно в 15–60 минут, чтобы получить deploy-роль уже из собственной инфраструктуры. Этого более чем достаточно, чтобы автоматизировать вывод данных или оставить бэкдор в облачной инфраструктуре.
Какие четыре уровня защиты нужно выстроить вокруг OIDC?
OIDC необходим, но сам по себе недостаточен. Вот четыре уровня защиты, которые теперь я считаю обязательными для любой архитектуры GitHub Actions, имеющей доступ к production.
Строгая политика доверия в облаке. Условие по claim sub в JWT нужно ограничивать максимально жестко: конкретный репозиторий, конкретная ветка или конкретное окружение. Избегайте широких условий StringLike и wildcard-шаблонов на уровне организации; по возможности используйте StringEquals для точного значения sub. Это первый барьер, который не дает workflow из форка или тестовой ветки получить production-роль.
Минимальные разрешения GitHub. id-token: write нужно задавать только для того job, которому он действительно нужен, и никогда — на уровне всего workflow. По умолчанию — contents: read. Подробности есть в руководстве Permissions GitHub Actions GITHUB_TOKEN.
Контроль исходящего трафика на раннере. Именно про этот уровень забывают чаще всего. Если вредоносный код не может связаться с инфраструктурой атакующего, вывести учетные данные наружу не получится. Harden-Runner в режиме блокировки или список разрешенных адресов/соединений в файрволе на self-hosted-раннере решают именно эту задачу.
Жесткий контроль изменений workflow. Каталог .github/workflows/ нужно считать таким же production-кодом: CODEOWNERS с обязательным ревью со стороны команды безопасности, фиксация сторонних action по SHA, строгие правила защиты веток. Именно такой урок дала атака на actions-cool/issues-helper, которая произошла всего за несколько часов до Megalodon.
Ни один из этих уровней не заменяет остальные. И ни один не делает OIDC необязательным. Они работают только вместе.
Как ужесточить политику доверия AWS после Megalodon?
Это ошибка, которую я чаще всего вижу во время аудитов: политику доверия AWS настраивают так, чтобы она «работала во всей организации», и в итоге делают слишком широкой для защиты от целевой атаки.
Вот шаблон, которого стоит избегать:
{ "Condition": { "StringLike": { "token.actions.githubusercontent.com:sub": "repo:my-org/*" } } }
StringLike с wildcard позволяет любому репозиторию организации получить эту роль. С любой ветки, из любого workflow и любого окружения. Стоит атакующему скомпрометировать какой-нибудь второстепенный репозиторий организации — и он сможет получить production-роль без дополнительных ухищрений.
А вот к чему стоит стремиться:
{ "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com", "token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:environment:production" } } }
StringEquals вместо StringLike. Claim sub ограничен конкретным репозиторием и конкретным окружением, а не веткой. Если атакующий получил возможность отправить коммит в main, с точки зрения проверки ветки он остается легитимным. Но чтобы получить роль через окружение production, ему сначала придется пройти обязательное подтверждение этого окружения. Защиту дает именно связка мер, а не каждый элемент по отдельности.
Сам по себе StringLike не запрещен: даже в документации GitHub есть примеры с wildcard, позволяющие использовать несколько веток или окружений одного репозитория. Проблема в слишком широком wildcard (repo:my-org/*). Если без StringLike не обойтись, ограничьте его строгим, документированным и протестированным шаблоном.
Точный формат claim sub зависит от триггера: ветка, тег, окружение или пулл-реквест. Сверяйтесь с документацией, прежде чем копировать пример в свою политику доверия: некорректный sub может заблокировать деплой без внятного сообщения об ошибке.
Почему GitHub Environments — недостающее звено?
GitHub Environments по-прежнему используют реже, чем стоило бы. И зря: они добавляют именно то, чего не хватает workflow с OIDC, — обязательное подтверждение человеком перед обменом токена.
Для production-окружения можно настроить:
обязательных ревьюеров — job не запустится без подтверждения человеком;
таймер ожидания — принудительную задержку перед выполнением;
разрешенные ветки — например, деплой в production только из main;
секреты, привязанные к окружению, — они недоступны другим job.
В связке с политикой доверия AWS, которая требует environment:production в claim sub, получается логичная цепочка защиты: нет деплоя без подтверждения человеком и нет валидного OIDC-токена без подтвержденного окружения. Защищенное окружение не сделало бы атаку Megalodon невозможной, но заметно изменило бы ее сценарий: внедренный workflow уже не смог бы незаметно получить production-токен, не пройдя через обязательное подтверждение, ограничение по ветке или явный контроль деплоя.
Именно этот уровень я чаще всего упускал в своих рекомендациях. Теперь не упускаю.
Что я изменил в собственных рекомендациях?
До мая 2026 года я говорил: «переходите на OIDC». И точка — будто отсутствие статических учетных данных само по себе решает проблему.
Теперь я говорю: «переходите на OIDC, И используйте защищенное окружение, И ограничьте политику доверия точным значением sub, И перекройте исходящий трафик раннера, И защитите .github/workflows/ с помощью CODEOWNERS». Пять требований вместо одного.
Звучит громоздче, зато честнее. OIDC по-прежнему остается правильной основой. Но один фундамент — еще не дом. А атаки вроде Megalodon напоминают: массовый атакующий не станет упираться в самую заметную стену, если рядом осталось открытое окно.
Какие ошибки я чаще всего вижу во время аудитов?
В аудитах GitHub Actions постоянно повторяются четыре ошибки:
id-token: write задан на уровне workflow, а не отдельного job. В результате любой job внутри workflow может запросить OIDC-токен, даже если он ему не нужен. Это первое, что стоит исправить.
Политика доверия с StringLike repo:my-org/*. Замените ее на
StringEqualsс точным значением sub, желательно привязанным к окружению.Для job развертывания не настроено защищенное окружение. Без обязательного подтверждения человеком любой коммит, прошедший CI, может попасть в production с доступом к облачным учетным данным.
Исходящий трафик раннеров никак не контролируется: нет ни Harden-Runner, ни списка разрешенных адресов/соединений в файрволе. Вредоносный код, укравший учетные данные, может без каких-либо сетевых ограничений вывести их наружу.
По отдельности ни одна из этих ошибок не выглядит катастрофической. Но вместе они создают именно те условия, на которые рассчитаны атаки семейства Megalodon.
Насколько хорошо вы разбираетесь в безопасности CI/CD и практиках DevSecOps? Проверить свою подготовку можно с помощью вступительного теста курса «Внедрение и работа в DevSecOps».
Чего я не утверждаю в этой статье?
Я не утверждаю, что OIDC — плохая практика.
Наоборот: OIDC по-прежнему предпочтительнее статических учетных данных для доступа к облаку, которые хранятся в GitHub Secrets. Он резко сокращает срок жизни учетных данных и позволяет ограничивать идентичность workflow через claims вроде repo, ref, environment или workflow.
Но OIDC не защищает от вредоносного workflow, которому уже разрешено запрашивать токен. Поэтому правильный вопрос звучит не «использовать OIDC или нет?», а так: «какой код может выполняться внутри job с id-token: write?»
Короткий чек-лист после Megalodon
Вот какую проверку я рекомендую провести для каждого репозитория в первые дни после инцидента вроде Megalodon. Она короткая, практическая, и в большинстве случаев на один проект хватит половины рабочего дня.
Проверьте все workflow, где объявлен
id-token: write.Перенесите
id-token: writeна уровень job и никогда не задавайте его глобально.Привяжите production-job к защищенному GitHub Environment.
Ограничьте облачную политику доверия точным значением sub.
Добавьте
CODEOWNERSдля.github/workflows/.Заблокируйте или как минимум контролируйте исходящий трафик раннеров.
Найдите workflow с именами
SysDiagиOptimize-Build— это индикаторы компрометации Megalodon.Проверьте все недавние коммиты в
.github/workflows/, начиная с 18 мая 2026 года.
Напоследок повторим основные мысли
OIDC убирает постоянные учетные данные для доступа к облаку, но не краткоживущие учетные данные, которые существуют внутри раннера во время выполнения job.
Megalodon и actions-cool эксплуатируют именно эту зону риска: атакующему достаточно украсть краткоживущий токен из памяти раннера.
В 2026 году одного OIDC уже недостаточно. Вокруг него нужны строгая политика доверия, минимальные разрешения, контроль исходящего трафика и жесткий контроль изменений workflow.
В политике доверия нужно использовать
StringEqualsс точным значением sub, включающим окружение, а неStringLikeсwildcardна всю организацию.GitHub Environments с обязательными ревьюерами — один из самых часто забываемых и при этом наиболее эффективных способов остановить внедренный workflow.
id-token: write— только на уровне job, никогда на уровне всего workflow.Каталог
.github/workflows/— это production-код: CODEOWNERS, обязательное ревью, фиксация сторонних action по SHA.

Больше о безопасности инфраструктуры, CI/CD и DevOps-практиках — на бесплатных открытых уроках OTUS. Выбирайте интересные темы в календаре мероприятий.
LMik
Ответ на вопрос элементарен - потому что гитхаб экшенс это кусок гуано на палке. можно было дальше и не рассуждать.