Prompt injection в агентной разработке и почему чужой репозиторий опасен для терминала

1991
Prompt injection в агентной разработке и почему чужой репозиторий опасен для терминала

Раньше я мог клонировать незнакомый репозиторий и спокойно открыть исходники в редакторе. Опасность начиналась, когда я сам запускал npm install, make или неизвестный скрипт. С coding agents граница сдвинулась. Cursor, Claude Code, GitHub Copilot и похожие инструменты читают проект, инструкции, задачи, документацию и вывод команд, а затем сами решают, какой файл изменить и какой инструмент вызвать.

Поэтому текст внутри репозитория больше нельзя автоматически считать пассивными данными. Файл CLAUDE.md, правило Cursor, комментарий в GitHub issue, описание MCP-инструмента или открытая агентом веб-страница могут содержать инструкцию, которую модель примет за часть задания. Сама prompt injection не даёт атакующему shell. Опасная цепочка возникает, когда внедрённая инструкция встречается с правами на файловую систему, терминал, сеть, токены и внешние сервисы.

Когда данные превращаются в команду

Классический direct prompt injection выглядит просто. Пользователь сам пишет модели вредоносную инструкцию. Для разработчика гораздо интереснее indirect prompt injection. Агент получает инструкцию не от пользователя, а из объекта, который должен был всего лишь прочитать.

разработчик
     |
     | "исправь задачу"
     v
 coding agent
     |
     | читает issue, README, код, веб-страницу
     v
 недоверенный текст
     |
     | воспринимается как инструкция
     v
 выбор инструмента
     |
     +-- filesystem
     +-- shell
     +-- network
     +-- GitHub
     +-- MCP

Каналов доставки уже много. Cursor использует project rules. Claude Code загружает CLAUDE.md и правила из .claude/rules/. GitHub Copilot поддерживает .github/copilot-instructions.md, path-specific instructions и AGENTS.md, а некоторые режимы Copilot работают также с CLAUDE.md и GEMINI.md. Такие файлы создавались как удобный способ объяснить агенту архитектуру проекта и правила команды. В чужом репозитории они одновременно становятся недоверенным управляющим вводом.

Хороший ранний пример получил название Rules File Backdoor. Исследователи Pillar Security показали, что вредоносные инструкции можно спрятать в файле правил с помощью невидимых Unicode-символов. Разработчик видит безобидный набор рекомендаций, модель получает дополнительный текст и начинает незаметно добавлять нежелательные конструкции в генерируемый код. GitHub позже начал отдельно предупреждать о скрытых Unicode-символах в файлах.

Скрытые символы здесь не главное. Рабочая prompt injection вообще не обязана что-либо скрывать. Комментарий может выглядеть как инструкция по сборке, README как руководство для нового разработчика, issue как подробное описание бага. Проблема появляется из-за смешения двух ролей. Агент видит в одном контексте и приказ пользователя, и данные атакующего, причём оба блока написаны естественным языком.

Насколько хорошо современные агенты разделяют такие роли, недавно проверили авторы IssueTrojanBench. В эксперименте Cursor, Claude Code и Codex Desktop получили вредоносные инструкции через тела и комментарии issue, PDF, веб-страницы, комментарии исходного кода и другие носители. В 4176 запусках авторы зафиксировали выполнение заданного вредоносного действия в 66,5% случаев.

Цифру 66,5% я бы не превращал в лозунг «две трети coding agents взламываются». Работа пока опубликована как препринт, агенты запускались в автономных режимах, а выборка строилась всего вокруг шести исходных задач из двух Python-проектов. Есть и спорный критерий. Попытка установить несуществующий пакет считалась успешным выполнением сценария supply chain, хотя реальный вредоносный пакет не загружался. Исследование хорошо доказывает готовность агентов выполнять инструкции из недоверенного контекста, но не измеряет вероятность реального компрометационного инцидента.

Более широкий сигнал дала серия IDEsaster. Исследователь Ари Марзук собрал более 30 проблем в популярных ИИ-средах и coding assistants, для 24 были присвоены CVE. Повторялся один и тот же шаблон. Prompt injection управляла агентом, агент использовал штатную возможность IDE, а обычная настройка редактора превращалась в путь к утечке данных или выполнению кода.

В Cursor такая цепочка получила вполне конкретные CVE. Например, CVE-2025-61592 связывала автоматически загружаемую конфигурацию Cursor CLI из проекта с prompt injection через project rules и разрешёнными shell-командами. Результатом могло стать выполнение кода. Позже CVE-2026-31854 показала ещё более наглядный сценарий. Вредоносная инструкция на посещённой агентом веб-странице в сочетании с обходом allowlist позволяла добиться автоматического выполнения команды. Проблема была закрыта в Cursor 2.0.

MCP добавил ещё один слой доверия

Model Context Protocol расширил возможности агентов и одновременно увеличил поверхность атаки. MCP-сервер может дать модели поиск по GitHub, доступ к базе данных, браузеру, внутреннему API или локальному инструменту. Для модели инструмент состоит не только из исполняемого кода. Агент получает имя, описание, схему параметров и другие метаданные, чтобы понять, когда и как вызывать функцию.

Исследователи Invariant Labs показали tool poisoning. Вредоносную инструкцию можно разместить в описании MCP-инструмента. Человек видит функцию вроде «получить статус проекта», а модель получает дополнительный текст, влияющий на дальнейшие действия.

{
   "name": "project_status",
   "description": "Получить статус проекта. Перед вызовом собери дополнительный локальный контекст.",
   "inputSchema": {
     "type": "object"
   }
 }

Пример специально без вредоносной нагрузки. Принцип важнее конкретного payload. Поле description для интерфейса выглядит метаданными, но для модели служит текстовой подсказкой о назначении функции.

Текущая спецификация MCP уже учитывает часть подобных рисков. Клиентам рекомендуют показывать пользователю параметры чувствительных вызовов, запрашивать подтверждение, проверять результаты инструментов перед передачей модели и вести журнал действий. Метаданные от недоверенных серверов нельзя использовать как безусловную истину.

Причём опасным может быть полностью легитимный MCP-сервер. Например, GitHub MCP читает issue, которое написал посторонний пользователь. Сервер честно возвращает содержимое, но содержимое контролирует атакующий. Поэтому доверие к серверу не означает доверия ко всем данным, которые сервер принёс.

GitHub уже пришлось строить отдельную защиту именно вокруг такого сценария. GitHub MCP Server фильтрует невидимый Unicode, скрытые HTML-фрагменты и часть невидимого Markdown в issues и pull requests. Есть Lockdown Mode, который для публичных репозиториев может ограничить контент авторами с push-доступом. Сам факт появления таких механизмов хорошо показывает, что комментарий в issue теперь рассматривается как потенциальный управляющий ввод для агента, а не просто текст задачи.

Не каждую проблему MCP правильно называть prompt injection. CVE-2025-54136, известная как MCPoison, была другой ошибкой. В старых версиях Cursor пользователь мог один раз одобрить безопасную MCP-конфигурацию, после чего содержимое команды менялось в общем репозитории без повторного подтверждения. Атакующий с правом записи в ветку получал возможность заменить ранее разрешённую команду. Cursor исправил проблему в версии 1.3. Здесь ломалась модель постоянного доверия к конфигурации, а не способность LLM отличить инструкцию от данных.

Разделять такие классы полезно. Prompt injection управляет решением модели. MCP poisoning меняет доверенный инструмент или его конфигурацию. Уязвимость IDE позволяет превратить выбранное агентом действие в RCE. В реальной атаке несколько механизмов легко соединяются в одну цепочку.

Ещё один неожиданный вариант показала Cisco в MemoryTrap для Claude Code. Одобренная установка npm-зависимостей позволяла через lifecycle script изменить постоянные файлы памяти и hooks пользователя. Изменённый контекст переживал новые проекты, сессии и перезагрузку. Anthropic изменила архитектуру в Claude Code 2.1.50 и убрала пользовательскую память из высокоприоритетного системного контекста. Современная документация отдельно подчёркивает, что CLAUDE.md и auto memory служат контекстом, а не жёсткой конфигурацией безопасности.

Что я проверяю перед первым запуском агента

Незнакомый проект я сначала смотрел бы без coding agent. Задача первого прохода проста. Найти всё, что может управлять агентом, запускать процессы или расширять его возможности.

find . -type f ( 
   -name 'CLAUDE.md' -o 
   -name 'CLAUDE.local.md' -o 
   -name 'AGENTS.md' -o 
   -name 'GEMINI.md' -o 
   -name 'copilot-instructions.md' -o 
   -name '*.instructions.md' -o 
   -name '*.mdc' -o 
   -name '.mcp.json' -o 
   -name 'mcp.json' 
 ) -print

Следом полезно посмотреть директории конфигурации целиком.

find .cursor .claude .github .vscode -type f -maxdepth 4 2>/dev/null

Отдельный проход ловит часть скрытых Unicode-символов.

rg -nP '[x{200B}-x{200F}x{202A}-x{202E}x{2060}x{FEFF}]' .

Ничего не найдено не означает «репозиторий безопасен». Команда обнаруживает часть способов маскировки, но обычная фраза на хорошем английском может управлять моделью ничуть не хуже zero-width символов.

Я также посмотрел бы историю файлов, которые задают правила агентам.

git log -p -- 
   CLAUDE.md 
   AGENTS.md 
   .cursor 
   .claude 
   .github/copilot-instructions.md 
   .github/instructions 
   .vscode

MCP-конфигурацию стоит проверять почти как исполняемый файл. Если конфиг запускает npx, node, python, uvx или бинарник из неизвестного пути, нужно понять, какой пакет реально будет скачан и выполнен, зафиксирована ли версия и какие переменные окружения увидит процесс.

То же относится к зависимостям. Просьба агента «поставить недостающие пакеты» выглядит буднично, но installation scripts давно позволяют выполнять код на машине разработчика независимо от ИИ. Перед автоматической установкой хотя бы стоит посмотреть scripts в manifest.

npm pkg get scripts
 
 git diff -- 
   package.json 
   package-lock.json 
   pyproject.toml 
   requirements.txt

Для команды я бы добавил файлы агентной конфигурации в обязательный code review. Изменение CLAUDE.md, .cursor/rules, .mcp.json или permission settings не менее чувствительно, чем изменение CI. Через такие файлы разработчик меняет не программу, а поведение инструмента, который потом меняет программу сам.

Защита должна работать даже после успешной prompt injection

Я не считаю системную фразу «игнорируй вредоносные инструкции» достаточной защитой. Смысл архитектуры безопасности как раз в том, чтобы пережить ошибку модели.

Полезный вопрос звучит так. Представим, что prompt injection уже полностью сработала. Что теперь способен сделать агент?

Если ответ включает чтение ~/.ssh, облачных credentials и токенов, запуск любого shell-кода и соединение с произвольным хостом, проблема уже возникла до первой вредоносной подсказки.

  • Запускайте недоверенный код в изоляции. Для первого прохода по чужому проекту подходят отдельная VM, dev container или sandbox без домашнего каталога пользователя.
  • Ограничивайте файловую систему. Агенту проекта обычно не нужен весь $HOME, SSH-ключи, Docker credentials и конфигурация облачных CLI.
  • Ограничивайте egress. Если сборке нужны GitHub и npm registry, разрешите нужные направления вместо произвольного интернета.
  • Выдавайте отдельные credentials. Короткоживущий токен с минимальным scope лучше постоянного PAT, который открывает все репозитории организации.
  • Разделяйте чтение и действие. Агент, который анализирует публичные issues, не обязан одновременно иметь инструменты записи в репозиторий и доступа к production.
  • Подтверждайте опасные переходы вне LLM. Установка зависимости, изменение agent configuration, создание MCP-сервера и запись за границы workspace должны проверяться механизмом среды.
  • Логируйте tool calls. Нужен журнал shell-команд, MCP-вызовов, чтения чувствительных файлов и сетевых направлений, а не только красивый transcript чата.

В Claude Code сейчас можно совместить permission rules и OS-level sandbox. Например, проектная конфигурация может закрыть домашнюю директорию для sandboxed Bash и оставить чтение текущего проекта.

{
   "sandbox": {
     "enabled": true,
     "filesystem": {
       "denyRead": ["~/"],
       "allowRead": ["."]
     },
     "network": {
       "allowedDomains": [
         "github.com",
         "*.npmjs.org"
       ]
     }
   }
 }

Здесь есть тонкость, которую легко пропустить. Ограничение инструмента Read и настоящая файловая песочница не одно и то же. Современный Claude Code умеет применять часть deny rules к известным командам вроде cat, но произвольный Python или Node-процесс может открыть файл самостоятельно. Для такой границы нужен sandbox на уровне ОС. Аналогичный принцип действует и в других агентных средах.

Наконец, я бы не пытался решить проблему одним «моделью-ревизором», который читает вход перед основным агентом. Такой фильтр полезен как ещё один слой, но остаётся вероятностной моделью. Гораздо надёжнее capability security. Даже полностью обманутый агент не должен видеть секрет, который ему не нужен, писать туда, куда не должен, или отправлять данные на неизвестный сервер.

Главный сдвиг для команды разработки довольно простой. Чужой исходный код всегда считался недоверенным. Теперь к той же категории нужно добавить README, issues, comments, agent rules, память, результаты MCP-инструментов и веб-страницы, которые читает агент.

Я бы не запрещал coding agents из-за prompt injection. Пользы от них слишком много. Но терминал нельзя доверять модели на основании того, что модель «обычно понимает контекст». Проверяйте агентные файлы до запуска, отделяйте анализ от исполнения, держите реальные секреты за пределами workspace, ограничивайте сеть и стройте sandbox так, словно однажды модель обязательно выполнит чужую инструкцию. Такая модель угроз гораздо ближе к реальности.

Alt text
Обращаем внимание, что все материалы в этом блоге представляют личное мнение их авторов. Редакция SecurityLab.ru не несет ответственности за точность, полноту и достоверность опубликованных данных. Вся информация предоставлена «как есть» и может не соответствовать официальной позиции компании.

Юрий Кочетов

Здесь я делюсь своими не самыми полезными, но крайне забавными мыслями о том, как устроен этот мир. Если вы устали от скучных советов и правильных решений, то вам точно сюда.