SSD Nodes Learn 🎉 VPS от $5.50/мес
Руководства Matt ConnorАвтор: Matt Connor

Prompt injection против coding agent: угрозы и защита

Узнайте, как README file, pull request и результаты инструментов превращаются в инструкции для coding agent, какие поверхности атак реальны и какие меры снижают ущерб.

Что такое prompt injection против coding agent

Prompt injection против coding agent можно определить просто: текст, который читает агент, воспринимается как инструкция, которой агент следует. Агент открывает файл, комментарий к pull request, веб-страницу или результат вызова инструмента. Всё это поступает в том же виде текста, что и ваш запрос. Если злоумышленник управляет любым таким текстом, он записывает инструкции в вашу сессию.

Такое свойство есть у всех современных продуктов с агентами. Модель получает одну последовательность токенов. Ваш запрос, system prompt, содержимое файлов и результаты работы инструментов объединяются, после чего модель прогнозирует продолжение. У токена нет признака уровня привилегий. В формате нет указания, какая часть была авторизована вами, а какая поступила из чужого README file.

Эта страница описывает модель угроз: каким образом текст под контролем злоумышленника попадает к агенту, работающему на сервере, что злоумышленник получает на каждом этапе и какие меры защиты оправдывают затраты. Рекомендации по containment в других руководствах имеют смысл только после того, как вы понимаете, от чего именно защищаете агент.

Почему модель не может отделить содержимое от инструкций

Обучение помогает, но не решает проблему. Современные модели обучены с осторожностью относиться к полученному тексту и отклоняют многие грубые попытки. Отказ — это вероятность, а не правило. Атакующий может переформулировать запрос, повторить попытку и скрыть текст в формате, который никто не предусмотрел. Количество вариантов формулировок ничем не ограничено.

Проект OWASP GenAI отслеживает эту проблему как LLM01:2025 Prompt Injection и разделяет её на два типа. Прямая инъекция — это изменение поведения модели с помощью собственного запроса пользователя. Косвенная инъекция — это изменение поведения модели под воздействием внешнего содержимого, например веб-сайта или файла, при его обработке. Для сервера важна именно косвенная инъекция, потому что агент читает значительно больше текста, чем вводите вы.

Первое систематическое исследование выполнили Greshake и его коллеги: Not what you've signed up for (2023). Их вывод нужно запомнить: если приложение передаёт полученный текст модели, которая может вызывать инструменты, обработка этого текста близка к произвольному выполнению кода.

Условие, при котором чтение превращается во взлом

Само по себе чтение враждебного текста не наносит ущерба. Для ущерба нужен канал вывода данных с машины.

В июне 2025 года Simon Willison назвал это сочетание смертоносной триадой. Агент, который имеет доступ к конфиденциальным данным, получает недоверенный контент и может отправлять данные наружу, можно заставить передать первые через третий.

У coding agent на вашем VPS с первого дня есть все эти условия. Конфиденциальные данные — это исходный код, файл `.env, ключи SSH и история shell. Недоверенный контент — это каждый репозиторий, веб-страница и результат работы инструмента, которые он читает. Каналом вывода могут быть git push, curl, npm publish`, тело pull request или ссылка, напечатанная в вашем терминале и открытая вами.

Удалить второе условие нельзя, поскольку чтение недоверенного текста — это задача, для которой вы наняли агента. Поэтому каждая практическая мера защиты должна работать с двумя другими условиями.

Куда попадает недоверенный текст при работе coding agent на сервере

Репозиторий, с которым работает agent

Каждый файл в checkout — это входные данные. Комментарии в исходном коде, README.md, журналы изменений, тестовые fixtures, сторонний код и сами файлы с инструкциями для agent: CLAUDE.md, AGENTS.md и их аналоги. Agent, которому поручено изучить codebase, читает их, потому что именно это ему поручено.

Здесь атакующий получает доступ ко всем, кто клонирует репозиторий и запускает в нём agent. Файлы с инструкциями — самый прямой путь, потому что их читают именно как инструкции. В pull request достаточно добавить в CLAUDE.md четыре полезные строки и одну строку, перенаправляющую agent. При поверхностной проверке человек может не обратить на это внимания.

Issues, pull requests и комментарии в code review

Любой текст, который посторонний может ввести в вашем tracker, попадает к agent, как только вы просите его выполнить triage. В мае 2025 года Invariant Labs опубликовала описание уязвимости GitHub MCP именно такого типа. Agent разработчика имел доступ к одному публичному репозиторию и к приватным репозиториям. Атакующий создал issue в публичном репозитории. Когда разработчик попросил agent проверить открытые issues, agent прочитал содержимое приватного репозитория и записал его в pull request в публичной части.

В этом отчёте описана архитектурная проблема, а не дефект кода MCP server. Agent использовал один токен с широкими правами, читал данные из публичного inbox и имел разрешение на запись. В обычном смысле конфигурация не была ошибочной, поэтому требуется ограничить область доступа, а не исправлять код.

Веб-страницы, которые загружает agent

Документация, ответы на форумах, страница vendor, результат поиска. Любой из этих источников может содержать текст, написанный для agent, а не для вас. Преобразование HTML в текст даёт атакующему дополнительное пространство, поскольку до model доходит даже содержимое, которое браузер не отображает.

Атакующий получает контроль в момент, когда за agent наблюдают меньше всего. Никто не читает полный текст страницы, которую agent загрузил во время поиска информации.

Результат MCP tool

MCP (model context protocol) — стандартный способ подключения agents к внешним tools. Результаты возвращаются в виде текста и сразу попадают в context window. Здесь есть две поверхности атаки, а не одна. Данные, которые возвращает tool, — очевидная поверхность. Другая — собственные имя и описание tool, которые model читает, чтобы решить, когда его вызвать. Server, которым вы не управляете, может изменить и то и другое между вызовами.

Атакующий, разместивший текст в результате одного tool, получает доступ ко всем остальным tools agent. Поэтому injection в инструменте с низкой ценностью может привести к выполнению действий в инструменте с высокой ценностью.

Логи CI, вывод сборки и метаданные зависимостей

npm install выводит текст из пакетов, которые написали не вы. При ошибке теста библиотека выводит сообщение assertion. Лог задания continuous integration (CI) может содержать тысячи строк вывода стороннего кода. Если попросить agent исправить неудачную сборку, он прочитает всё содержимое.

В этом случае атакующий получает доступ к build machine. Обычно на ней хранятся credentials для deploy и registry tokens, а контролируют её реже, чем laptop.

Что на самом деле получает атакующий

Нужно учитывать четыре сценария.

Кража учётных данных. Всё, что может прочитать процесс агента, считается доступным: переменные окружения, ~/.aws/credentials, ~/.ssh, токен gh, файл конфигурации Docker. Для их передачи наружу не требуется curl. Коммит в ветку, описание pull request, пакет, опубликованный в registry, или DNS-запрос к имени, которым управляет атакующий, — всё это позволяет вывести данные с сервера.

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

Закрепление. Один раз записанный файл продолжает работать без участия модели: hook в .git/hooks, скрипт postinstall в package.json, строка, добавленная в файл запуска shell, или дополнительная строка в CLAUDE.md. Следующая команда запустит этот код.

Перемещение внутри вашей сети. Агент запускается там, где вы его разместили. Если этот сервер имеет доступ к базе данных через loopback, внутреннему сервису администрирования, metadata service вашего cloud provider или другому хосту в private network, такой доступ получает и любой компонент, управляющий агентом.

Режимы автоматического подтверждения убирают последнюю проверку

В режиме по умолчанию Claude Code запрашивает подтверждение перед выполнением команды или изменением файла. Этот запрос пользователя служит проверкой перед каждым из описанных выше действий. Режимы, в которых запрос не выводится, убирают эту проверку.

В документации прямо указано следующее о bypassPermissions: используйте его только в изолированных средах, например в контейнерах или виртуальных машинах, где Claude Code не может причинить вред. Автоматический режим менее строгий: он автоматически подтверждает вызовы инструментов и выполняет фоновые проверки безопасности, которые определяют, соответствуют ли действия вашему запросу. Эти проверки выявляют многие проблемы. Однако они по-прежнему основаны на оценке модели результатов работы модели. Поэтому считайте их фильтром, а не границей безопасности.

Администратор может отключить обе проверки. Укажите permissions.disableBypassPermissionsMode или permissions.disableAutoMode как "disable" в файле настроек и поместите этот файл в managed settings, чтобы проект после checkout не мог переопределить эти параметры. В нашем руководстве автоматический режим Claude Code и правила разрешений описано, где применяется каждое правило.

Защита: порядок по полезности

Ни один из этих пунктов не устраняет проблему полностью. Каждый либо ограничивает данные, которыми располагает агент, либо ограничивает его действия с этими данными.

  1. Машина, которую можно уничтожить и заново собрать. Тогда компрометация обойдётся вам часом работы, а не полноценным инцидентом.
  2. Учётные данные, отдельные от ваших, ограниченные одним репозиторием и имеющие короткий срок действия.
  3. Отсутствие долгоживущих секретов в окружении, которое наследуют команды агента.
  4. Ограничение исходящего сетевого трафика и доступа к файлам на уровне операционной системы. Оно применяется ко всем процессам, которые запускает агент.
  5. Включённые запросы на подтверждение операций записи и сетевых вызовов.
  6. Hooks как детерминированный резервный механизм для конкретных действий, которые можно явно описать.
  7. Проверка diff перед слиянием изменений.

Порядок имеет значение. Пункты 1–4 сохраняют силу, даже если модель полностью контролируется атакующим. Пункты 5–7 зависят от внимания человека. Именно внимание обычно пропадает при длительном запуске агента.

Поместите агента на машину, которую можно выбросить

VPS с checkout и одним токеном с ограниченными правами представляет собой гораздо меньшую ценность для атакующего, чем ноутбук с вашими ключами. Запускайте агента от отдельного непривилегированного пользователя, а не от своей учётной записи и не от root. В руководствах по disposable VM для coding agents и пользователям с минимальными правами на VPS описана настройка, а в руководстве по безопасному запуску Claude Code на VPS — ежедневная схема работы.

Уберите секреты из окружения

Переменная окружения доступна каждому дочернему процессу, то есть каждой команде, которую запускает агент. Sandbox Claude Code может удалять именованные переменные перед выполнением каждой команды в sandbox. Сначала установите в Linux два пакета:

sudo apt-get install bubblewrap socat

Затем в ~/.claude/settings.json:

{
  "sandbox": {
    "enabled": true,
    "network": {
      "allowedDomains": ["github.com", "*.npmjs.org"]
    },
    "credentials": {
      "envVars": [
        { "name": "GITHUB_TOKEN", "mode": "deny" },
        { "name": "NPM_TOKEN", "mode": "deny" }
      ]
    }
  }
}

Запись deny удаляет эту переменную перед запуском каждой команды в sandbox, а allowedDomains содержит список хостов, к которым разрешены команды в sandbox. Блок credentials требует Claude Code v2.1.187 или более поздней версии; это проверено в August 2026. Выполните /sandbox в сессии, чтобы увидеть активные уровни и отсутствующие зависимости. Определить, какие секреты вообще должны находиться на этой машине, — это большая часть задачи. В руководстве по ограничению доступа AI agent к секретам этот вопрос разобран подробно.

Ограничьте исходящий сетевой трафик на уровне операционной системы

Правило firewall не зависит от решения модели. Запустите агента от выделенного пользователя agent, а затем заблокируйте трафик, который отправляет этот пользователь:

table inet agentcage {
  chain output {
    type filter hook output priority filter; policy accept;
    meta skuid "agent" ct state established,related accept
    meta skuid "agent" oif lo accept
    meta skuid "agent" counter drop
  }
}

После этого у пользователя agent останется только loopback-доступ. Его трафик должен проходить через proxy, запущенный на той же машине, а proxy будет использовать allowlist имён хостов. Если https_proxy указывает на этот proxy, клиент отправляет запрос CONNECT, а proxy выполняет разрешение имени. Поэтому агенту не нужен собственный исходящий DNS (domain name system). Проверьте настройку с помощью sudo nft list ruleset и наблюдайте, как увеличивается счётчик правила блокировки, когда агент пытается обратиться к новому адресу.

Во время изменения правил firewall держите открытой вторую SSH-сессию. Также проверьте, как container runtime обрабатывает эти правила: Docker создаёт собственные цепочки, а в руководстве о том, как опубликованные порты Docker обходят ufw описана причина такого поведения.

Hooks: проверка, которую модель не может обойти аргументами

Правила разрешений и hooks применяются Claude Code, а не моделью. В документации это сформулировано прямо: инструкции в prompt или CLAUDE.md определяют, что Claude пытается сделать, но не изменяют действия, разрешённые Claude Code. В этом и состоит основная ценность такого разделения. Строка в CLAUDE.md с текстом "never run curl" — это рекомендация, с которой внедрённый текст может спорить. Hook — это процесс, который возвращает код завершения.

Зарегистрируйте hook PreToolUse в .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/no-egress.sh"
          }
        ]
      }
    ]
  }
}

Hook получает вызов инструмента в формате JSON через стандартный ввод. Код завершения 2 блокирует вызов и показывает Claude причину из стандартного вывода ошибок. Код завершения 0 пропускает вызов в обычный процесс проверки разрешений.

#!/usr/bin/env bash
# PreToolUse: stdin holds the tool call, exit 2 blocks it.
cmd=$(jq -r '.tool_input.command // ""')
if printf '%s' "$cmd" | grep -qE '(^|[;&|]|\s)(curl|wget|nc|ncat)(\s|$)'; then
  echo "Blocked: this repository does not allow outbound network commands." >&2
  exit 2
fi
exit 0

Теперь о важном ограничении. Это denylist для строки shell-команды, а denylist для строк shell-команд можно обойти. python3 -c открывает socket, не используя слово curl. Цель make deploy скрывает тот же вызов ещё на один уровень. Пишите hooks для ошибок, которые можно явно описать, а реально важную границу размещайте в kernel или в сети.

Правила запрета разрешений имеют ограничение сопоставления, о котором нужно знать. Правила Read и Edit распространяются на собственные файловые инструменты Claude и на файловые команды, которые он распознаёт в Bash, например cat, head, tail и sed. Они не распространяются на скрипт Python или Node, который самостоятельно открывает файл. Правила проверяются в порядке deny, затем ask, затем allow. Поэтому правило deny не может содержать исключение в виде allowlist.

{
  "permissions": {
    "deny": [
      "Read(.env)",
      "Read(./secrets/**)",
      "Bash(git push *)"
    ]
  }
}

Контролируйте исходящие данные и проверяйте diff

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

Что пока не решено

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

Фильтры помогают, но иногда ошибаются. Классификатор, который обнаруживает большинство попыток инъекций, всё равно должен правильно сработать каждый раз, тогда как атакующему достаточно преуспеть один раз. Поэтому опубликованный показатель эффективности защиты — это отправная точка для следующей попытки, а не гарантия.

Наиболее перспективные исследования посвящены уровню проектирования, а не уровню модели. CaMeL из работы Defeating Prompt Injections by Design (Debenedetti и коллеги, 2025) сначала извлекает поток управления и поток данных из доверенного запроса. Благодаря этому недоверенные данные не могут изменить поведение программы. Затем система проверяет разрешения при вызове инструментов. Собственные показатели статьи на бенчмарке AgentDojo показывают цену такого подхода.

ChartAgentDojo tasks solved, published figures from the CaMeL paper (2025)
The data behind this chart
[
  {
    "label": "Undefended agent",
    "tasks_solved_pct": 84
  },
  {
    "label": "CaMeL",
    "tasks_solved_pct": 77
  }
]

Агент без защиты решил 84 процентов задач. CaMeL решил 77 процентов задач с гарантией безопасности. Это опубликованные в статье показатели для одного бенчмарка, а не измерение вашей рабочей нагрузки. Разрыв между ними примерно показывает, сколько сегодня стоит реальная гарантия.

Пока такой подход не реализован в инструментах, которыми вы пользуетесь ежедневно, исходите из того, что агент в какой-то момент будет скомпрометирован, и сделайте это событие незначительным. Именно поэтому нужны отдельная одноразовая машина, ограниченные учётные данные, контролируемый исходящий трафик и привычка просматривать diff.

FAQ

Можно ли остановить prompt injection, если указать агенту игнорировать инструкции в файлах?

Нет. Эта фраза находится в том же контекстном окне, что и атака, поэтому для агента она имеет такой же приоритет, как и текст атакующего. В документации Claude Code это различие описано явно: инструкции в prompt или CLAUDE.md определяют, что агент пытается сделать, но не изменяют разрешения инструмента. Рассматривайте файл с инструкциями как описание намерений. Всё, на что вы рассчитываете, задавайте в правилах разрешений, хуке PreToolUse или правилах firewall.

Представляет ли prompt injection реальную угрозу, если агент работает только с моим репозиторием?

Да, поскольку в репозитории много текста, написанного не вами. Файлы README зависимостей, URL в lockfile, тестовые фикстуры, vendored code и вывод npm install появляются в ходе обычной задачи. Всё, что загружается из issue tracker или с сайта документации, поступает таким же образом. Риск увеличивается вместе с объёмом данных, которые читает агент. Полезный агент читает много.

Решает ли эту проблему запуск агента в контейнере?

Он ограничивает ущерб, но только если вы также заберёте у агента учётные данные. Контейнер с перенаправленным SSH agent, облачными учётными данными в окружении и неограниченным сетевым доступом предоставляет атакующему почти те же возможности, что и хост. Главное преимущество контейнера — файловая система, которую можно удалить, и изолированное место для принудительного применения правил исходящего трафика. Используйте его вместе с токеном, ограниченным одним репозиторием.

Какое одно изменение сильнее всего снижает риск?

Удалите долгоживущие учётные данные из окружения, которое наследуют команды агента, а затем задайте для этой машины политику исходящего трафика с запретом по умолчанию. Вместе эти меры устраняют третье условие lethal trifecta: текст всё ещё может перехватить управление агентом, но данным, к которым он получает доступ, некуда передаваться с пользой для атакующего. Запросы на подтверждение и проверка diff тоже помогают. Однако они требуют, чтобы человек сохранял внимание во время длительного запуска, поэтому уступают этим двум мерам.