Назад к блогу

Plugin4Shell: как подмена Git-ветки обходит закрепление коммита в кодинг-агентах

Plugin4Shell: как подмена Git-ветки обходит закрепление коммита в кодинг-агентах

Исследователи AIR Security раскрыли Plugin4Shell — zero-click RCE, затрагивающий Claude Code, Codex, GitHub Copilot и Gemini CLI: подмена Git-ветки позволяет атакующему подсунуть чужой код вместо закреплённого коммита, причём агент отчитывается об успешной установке проверенной версии. Уязвимость охватывает миллионы установок и срабатывает без каких-либо действий разработчика благодаря фоновому автообновлению плагинов. Разбор механики эксплуатации показывает, как одного пропущенного шага проверки достаточно, чтобы обрушить всю модель доверия к цепочке поставок агентных расширений.

Исследователи AIR Security описали Plugin4Shell — zero-click RCE в цепочке поставок плагинов. Уязвимость затрагивает четыре крупнейших ИИ-агента для кодинга: Claude Code, Codex, GitHub Copilot и Gemini CLI. Атакующий, получивший контроль над репозиторием плагина, заставляет агент установить чужой код вместо закреплённого коммита — и делает это без установки, запроса или иного заметного действия со стороны разработчика.

Оценка охвата — миллионы установок агентов. Нулевой клик обеспечивается фоновым автообновлением: агент сам перепроверяет и переустанавливает плагины, поэтому подмена доходит до уже установленных расширений.

Что именно изменилось

Речь не о новом классе атак, а о конкретном пропущенном шаге проверки. Все затронутые агенты выполняют checkout закреплённого коммита, но не проверяют, что в рабочем каталоге оказался именно он. Закрепление коммита (pinned SHA) — это привязка плагина к конкретному Git-коммиту, чтобы последующие изменения репозитория не влияли на одобренную версию; SHA. Этого одного пропуска достаточно для эксплуатации.

Механика опирается на поведение Git при неоднозначности имён. Ссылка (ref) — это именованная метка в репозитории, например ветка, указывающая на коммит; объект — это сам коммит, на который метка указывает. Если имя одновременно является допустимой ссылкой и идентификатором объекта, Git предпочитает ссылку и лишь печатает предупреждение о неоднозначности. Этим и пользуется атакующий.

Для Claude Code, Codex и GitHub Copilot схема такая: атакующий создаёт ветку, имя которой в точности совпадает с 40-символьным SHA закреплённого коммита, и делает её веткой по умолчанию репозитория. При checkout Git разрешает имя в ветку, а не в коммит, и в рабочем каталоге оказывается код атакующего. Агент при этом сообщает об успешной установке закреплённого коммита.

У Gemini CLI механизм другой. Клиент получает коммит через git fetch, а затем выполняет git checkout FETCH_HEAD. FETCH_HEAD. Здесь git checkout FETCH_HEAD не обязан читать файл .git/FETCH_HEAD: если ветка по умолчанию сама называется FETCH_HEAD, checkout разрешается в неё, а полученный коммит молча отбрасывается в пользу содержимого ветки атакующего.

Условие срабатывания для не-Gemini варианта: ветка с именем-хешем должна быть веткой по умолчанию. Ветка, не являющаяся веткой по умолчанию, загружается только как remote-tracking ref, и checkout откатывается к коммиту.

Предыстория

Plugin4Shell — атака на слой распространения, то есть на маркетплейсы, через которые агентные дополнения попадают на машины. Это не атака на модель или самого агента, а эксплуатация доверия к плагинам, которые агент устанавливает и обновляет автоматически.

В более широком контексте это продолжение серии инцидентов, показывающих, что агент выполняет то, что читает. EchoLeak (CVE-2025-32711, CVSS 9.3), найденный Aim Labs в Microsoft 365 Copilot, — первая публично описанная zero-click атака на ИИ-агента: RAG подтягивает в контекст письмо со скрытыми инструкциями, и пейлоад срабатывает без действий пользователя. CVE-2026-22708 против Cursor показал, что отравление переменных окружения через шелл-встроенные команды обходит allowlist, и одобренные команды вроде git branch становятся носителями пейлоада. Sentry agentjacking (Tenet Security, июнь 2026): в issue-трекер подкладывается «предложение по исправлению» с командой запуска пакета атакующего, агент получает это через MCP как обычный результат инструмента и запускает пакет. s1ngularity (Wiz, август 2025): скомпрометированный пакет Nx печатает в stdout postinstall-скрипта текст, адресованный агенту, и тот публикует инвентарь секретов в публичный репозиторий. Comment-and-Control (апрель 2026): инъекция через заголовки и комментарии в pull request заставляет агента выполнить ps auxeww и запостить переменные окружения обратно в комментарий. Pandora (Trend Micro) при загрузке вредоносного документа Word извлекла встроенную Python-полезную нагрузку и выполнила её, передав данные на C&C.

Масштаб предыдущих эпизодов подтверждает порядок величин: в истории Skills было захвачено более 26 000 агентов, в SkillJacking перехвачено 925 навыков, что затронуло 134 000 агентов.

Почему это сделали возможным

Корневая причина — единственная: агент выполняет checkout закреплённого коммита, но не проверяет, что рабочее дерево действительно оказалось на нём. Атакующий получает контроль над репозиторием двумя путями: либо сам публикует изначально безобидный плагин, проходит проверку и позже подменяет его содержимое на вредоносное, либо захватывает репозиторий, стоящий за плагином другого автора, которому маркетплейс уже доверяет.

Фоновое автообновление превращает подмену в zero-click. Агенты обновляют установленные плагины в фоне, и в Claude Code и Codex это поведение по умолчанию. Тот же checkout запускается повторно, поэтому когда маркетплейс поднимает закреплённый SHA, подмена доходит до уже установленных плагинов без действий пользователя. Уже установленный и доверенный плагин заменяется вредоносной версией без установки, запроса или иного заметного действия.

Plugin4Shell эксплуатирует доверие к автоматически обновляемым плагинам: доверенный плагин незаметно подменяется вредоносным и автоустанавливается в обход SHA-пиннинга, что даёт полную компрометацию агента и хоста, а значит полный доступ ко всем активам и данным, до которых агент может дотянуться.

Что это меняет на практике

Исправление должно поставляться в самом агенте: закрепление разрешается на стороне клиента, поэтому ни один маркетплейс не может обеспечить гарантию, которую рекламирует. Одного контроля каталога расширений недостаточно, поскольку ошибка возникает на стороне агента при разрешении Git-ссылки.

Закрывающая проверка сравнивает фактически оказавшийся в рабочем дереве коммит с закреплённым SHA и прерывает установку при несовпадении:

test "$(git rev-parse HEAD)" = "<pinned-sha>" || abort

Проверять нужно именно разрешённый HEAD, а не запрошенный ref — эту разницу пропускает вариант Gemini. И проверка обязана выполняться внутри агента.

Статус исправлений по агентам различается. Anthropic закрыла уязвимость в Claude Code 2.1.179, OpenAI исправила Codex в 0.146.0 — эти агенты следует обновить до указанных версий. Для GitHub Copilot исправление не выпущено: уязвимость раскрыта Microsoft, но патча у пользователей нет. Gemini CLI объявлен устаревшим и патчиться не будет, поэтому каждая установка остаётся уязвимой навсегда; пользователям рекомендуется перейти на Antigravity, до которого эта атака не достаёт — там нет закрепления SHA для плагинов маркетплейса, которое можно было бы обойти.

Ограничения и открытые вопросы

Сканер навыков Anthropic не закрывает этот класс атак. Вредоносные навыки обходят его теми же старыми приёмами, которые работают против любого LLM-based static scan, включая реальные образцы в дикой природе. Причина архитектурная: любой текст в контекстном окне может влиять на поведение модели, и она не отличает доверенные инструкции от недоверенных данных — все входы имеют одинаковый вес доверия. Если недоверенный текст попадает в то же контекстное окно, что и системные инструкции, никакой постфактум-фильтр не закроет дыру надёжно. Классификаторы и гардрейлы работают против вчерашних атак и проваливаются против завтрашних.

Классификатор auto mode структурно слеп к каналу непрямых инъекций: результаты инструментов вырезаются из его входа, а сам он — удобство на основе best-effort классификатора, а не гарантия безопасности. Диалог подтверждения не знает, что агент минуту назад прочитал README с инструкцией запустить ровно эту команду. Пользователи подтверждают 97% запросов разрешения, ловя лишь 13,6% опасных команд, тогда как классификатор — 89%. Самодельные хуки-защиты контент-слепы: они судят строку команды на PreToolUse и ничего не знают о прочитанном.

На архитектурном уровне помогает защита в глубину: даже если инъекция сработает на уровне модели, следующий слой инструментального контроля увидит происхождение и сможет заблокировать действие. OWASP формулирует это как segregation of trust и подчёркивает, что в 2026 году prompt injection и memory/RAG poisoning делают такое разделение архитектурным требованием, а не просто хорошей практикой промпт-инжиниринга. Изоляция инструментов означает предоставление каждому инструменту минимально необходимого набора прав и работу в изолированном контексте. Human-in-the-loop — архитектурный компонент, встроенный в поток выполнения агента, где действия классифицируются по уровню риска и только низкорисковые выполняются автоматически.

Авторы перечисляют и честные ограничения собственных защитных механизмов. Латентность хука — 100–250 мс на вызов, причём доминирует старт процесса Node, а не сам скан; локальный демон лишь в планах. ReDoS смягчён, но не устранён: начавшийся матч нельзя прервать, бюджет по времени проверяется только между правилами и вариантами, а при выходе за бюджет результат трактуется как подозрительный. Обрезание хвоста аудит-лога не детектируется, пока не появятся подписанные чекпоинты. Обходы через shell quote-splicing (c"u"rl, $'curl') известны, quote-aware лексер в планах. Отслеживание происхождения команды работает на уровне текста: агент, перефразировавший команду, по происхождению не поймается. Secret egress смотрит только на значения в аргументах, а раскрытие $VAR происходит в shell уже после того, как защита увидела команду. Ложные срабатывания реальны: как только вывод помечен подозрительным, все его части считаются продиктованными им, включая легитимные setup-команды. И общее для всех hook-based решений: защита не принимает решения о вызове, который агент не пропустил через хук. Открытым остаётся вопрос, полезно ли прерывание отслеживания происхождения для тех, кто гоняет агентов в auto mode.

Источники

Похожее