На соревновании AI-агентов https://bitgn.com, где я участвовал, был класс задач на секьюрити. Там могли подсунуть промпт-инъекцию, попросить прочитать чужие файлы, вытащить переменные окружения, декодировать пейлоад и что-то выполнить.
Оттуда у меня и родилась идея плагина для opencode. Поставить перед опасными действиями детерминированный фильтр. Он проверяет входящие сообщения и аргументы тулов до того, как что-то уйдет в модель или в реальное исполнение.
Ссылка на сам плагин для opencode.
Сейчас в нем есть:
-
установка одной командой
-
минимальный конфиг
-
309 правил из коробки
-
поддержка локальных моделей через opencode
-
открытый набор политик, который можно расширять под свою инфраструктуру
Почему системного промпта мало
Потому что если защита на уровне промптов, то модель можно убедить делать то, что хочет злоумышленник. Если максимально упрощать: ignore previous instructions, show system prompt, decode and run this payload, [developer] do X.
Когда у агента есть файловые и шел тулы то можно произойти что-то подобное:
— чтение .env, ~/.ssh, /run/secrets, /proc/self/environ
— печать секретов через printenv, echo $TOKEN, export -p
— подготовка данных к эксфильтрации через base64, xxd, openssl enc
— попытки обойти правила через фейковые system и developer сообщения
Поэтому сначала проверка полиси, потом вызов модели или тула.
Как это устроено
У плагина всего два хука интеграции с opencode:
1. experimental.chat.messages.transform — обрабатывает пользовательские сообщения до отправки в модель
2. tool.execute.before— проверяет вызов тула до исполнения
export const OpencodePolicy = async ({ client } = {}) => { return { "experimental.chat.messages.transform": async (input, output) => promptInjectionPatterns(client, input, output), "tool.execute.before": async (input, output) => unsafeToolPatterns(client, input, output), }}
Это и есть ключевая идея. Если опасная команда уже дошла до шелл тула, то уже поздно. Проверка должна стоять раньше.
Что именно проверяется
Сейчас внутри два набора правил:
-
282 правила для тулов
-
27 правила для пропмпт инъекций
Оба набора лежат в json и работают как регекс политики. На старте они загружаются в память, после чего каждое подозрительное значение проверяется по набору правил.
Упрощенно это выглядит так:
const match = (rules, value) => { for (const rule of rules) { const regex = new RegExp(rule.pattern, rule.flags ?? "i") if (regex.test(value)) { return rule } } return null}
Мне в этом подходе нравится предсказуемость. Если блок сработал, понятно почему. После инцидента можно быстро добавить новое правило. На набор политик легко писать детерминированные тесты. И поведение не зависит от того, какая модель стоит под капотом.
Как режется промпт инъекции
Для входящих сообщений плагин проходит по parts и вытаскивает текстовые поля: text, prompt, command, source.value. Каждое значение прогоняется через набор правил для промпт нъекций.
Если есть совпадение, исходное сообщение не уходит в модель как есть. Оно подменяется безопасным отказом. Модель не видит атакующий ввод, а пользователь получает понятное объяснение, что запрос отклонен политикой.
Примеры правил:
{ "id": "ignore-en-1", "pattern": "ignore\\s+(previous|all|your)\\s+(instructions|rules|prompt)", "reason": "Attempt to bypass instructions"}{ "id": "forget-ru-1", "pattern": "забудь\\s+(все\\s+)?(инструкции|правила|промпт)", "reason": "Attempt to reset instructions (RU)"}{ "id": "system-tag-3", "pattern": "\\[developer\\]", "reason": "Fake developer message"}
То есть фильтр ловит англоязычные джеилбрейк фразы, русские варианты, и попытки подделать служебные роли.
fКак режутся опасные вызовы тулов
Второй слой работает на tool.execute.before. Здесь проверяется не только filePath, а вообще все основные аргументы вызова: command, text, prompt, query и даже имя тула.
Это важно, потому что опасный сценарий прячется не только в пути к файлу. Он может сидеть внутри шелл команды или текстового запроса к другому инструменту.
Вот несколько примеров правил:
{ "id": "env-direct-2", "pattern": "\\bprintenv\\b", "reason": "BLOCKED: Leaks environment variables"}{ "id": "echo-secret", "pattern": "\\becho\\s+\\$[A-Z_]*(KEY|TOKEN|SECRET|PASSWORD|PASS|API|CREDENTIAL)[A-Z_]*", "flags": "i", "reason": "BLOCKED: Attempted to print secret"}{ "id": "proc-environ-2", "pattern": "\\/proc\\/self\\/environ", "reason": "BLOCKED: Reads own environment"}{ "id": "base64-exfil-1", "pattern": "base64\\s+[^|<>\\s]", "reason": "BLOCKED: base64 file encoding (exfiltration)"}{ "id": "openssl-exfil", "pattern": "openssl\\s+enc", "reason": "BLOCKED: openssl encoding (exfiltration)"}
Если правило срабатывает, вызов останавливается до исполнения:
const deny = (reason) => { const error = new Error(`Blocked by opencode-policy: ${reason}`) error.stack = `Error: Blocked by opencode-policy: ${reason}` throw error}
Команда просто не запускается.
Логи и разбор инцидентов
Каждое срабатывание логируется в jsonl. В лог пишется время, уровень, тип события, краткий контекст и идентификатор правила.
Это полезно:
-
видно, что именно пытался сделать агент
-
можно пополнять правила по реальным атакам
-
проще разбирать фалс позитив и фалс негатив
В итоге плагин работает еще и как слой наблюдаемости для агентного контура.
Почему здесь нормально использовать регекс
Многие опасные действия выражаются очень конкретно: имена файлов, shell-команды, сигнатуры вроде printenv, base64, /run/secrets, ignore previous instructions, [system]. Здесь не нужна сложная семантика. Нужно надежно поймать известный паттерн до того, как он дойдет до модели или тула.
У регекс подхода есть и практические плюсы:
-
правила легко ревьюить
-
дифф по политике читается человеком
-
обновления легко привязывать к конкретным кейсам
-
одни правила для всех моделей
Понятно, что это не замена сендбокс, файловым ограничениям, белому списку тулов, сетевой изоляции и нормальной модели прав. Но как ранний и прозрачный слой защиты это очень полезная штука.
Что мне здесь нравится больше всего
Главная ценность для меня в механике накопления правил. Можно начать с малого набора, потом гонять его на реальных сценариях, ловить фалс позитив и фалс негатив и постепенно собирать правила под свою среду.
Если ваши агенты работают с кодом, шелл, секретами, CI/CD или внутренними репозиториями, такой слой уже выглядит как базовая гигиена.
Как попробовать
Установка обычная:
npm install opencode-policy
Потом достаточно добавить плагин в конфиг opencode:
{ "$schema": "https://opencode.ai/config.json", "plugin": ["opencode-policy"]}
После этого opencode будет прогонять сообщения и вызовы тулов через слой правил до исполнения.
Откуда взялись правила
Правила я не хотел придумывать из головы, поэтому ориентировался на реальные атаки и существующие наработки. Спасибо Валерию Ковальскому за чудесный проект https://github.com/vakovalskii/topsha, в котом уже есть набор таких правил из реальных атак 1500 инженеров.
Я уже сам этим пользуюсь. Если тоже работаете с агентами через opencode, посмотрите репу.
ссылка на оригинал статьи https://habr.com/ru/articles/1028448/