Я поставил перед агентом перехватчик разрушительных команд и шестнадцать дней писал в журнал всё, что агент делал. Хотел получить красивую статистику предотвращённых катастроф. Получил единицу.
Дальше — что было в журнале и почему единица оказалась интереснее, чем то, на что я рассчитывал.
Зачем вообще считать
Историй про агентов, снёсших чужие данные, хватает.
В апреле агент Cursor удалил продакшн‑базу PocketOS за девять секунд. Ни взлома, ни prompt injection: агент увидел несоответствие креденшелов, нашёл токен с подходящими правами и выполнил одну GraphQL‑мутацию к API Railway. Мутация снесла том — вместе со всеми бэкапами, потому что Railway хранил их там же. Последняя пригодная копия оказалась трёхмесячной давности.
В декабре на r/ClaudeAI собрал полторы тысячи апвоутов пост «Claude CLI удалил мой домашний каталог». Человек попросил прибраться в старом репозитории, агент собрал команду удаления с лишним ~/ на конце.
Проблема этих историй в том, что они рассказываются постфактум и только тогда, когда что‑то сломалось. По ним нельзя понять ни как часто такое случается, ни — что важнее для практики — сколько будет стоить защита в повседневной работе. Инструмент, который дёргает пять раз в день, снесут в первую же неделю, даже если он однажды спас бы вам базу.
Измерений я нигде не нашёл. Поэтому померил у себя.
Из чего состоит работа агента
Шестнадцать дней, четыре сессии, 1496 зафиксированных действий (снимок журнала на 24 августа):
Действие | Сколько | Доля |
|---|---|---|
выполнение bash‑команд | 853 | 57,0% |
правки файлов | 244 | 16,3% |
чтение файлов | 124 | 8,3% |
запись новых файлов | 97 | 6,5% |
остальное (поиск, задачи, MCP) | 178 | 11,9% |
Больше половины — bash. Не правки кода, ради которых агента вроде бы и зовут, а команды в оболочке: ssh, psql, curl, docker, установка пакетов, деплой.
У этого есть неприятное следствие. Встроенные механизмы отката — /rewind в Claude Code, Checkpoints в Cursor — работают на уровне файловых правок. Изменённый исходник они вернут прекрасно. Что произошло внутри psql или на том конце ssh, они не видят и вернуть не могут. Строка, удалённая из базы, отправленный запрос к платёжному API, прошедший деплой — всё это вне их досягаемости, а это больше половины того, чем агент занят.
Ещё цифра: из 853 команд уникальных 836. Агент почти не повторяется. Читать такой поток глазами невозможно физически, и именно поэтому люди включают автоодобрение — а дальше уже не важно, что там предлагается выполнить.
Шестьдесят перехватов, из которых настоящий один
В журнале перехватчика 60 записей. Цифра для лендинга отличная. Разбираем.
45 записей оказались тестовыми фикстурами. Это payload'ы из моего же набора тестов — DROP TABLE users;, psql -c "DROP DATABASE prod;" и ещё четыре десятка похожих. Они пишутся в журнал при каждом прогоне тестов.
Отдельно скажу, как я на этом чуть не погорел. Первый фильтр, который я написал для разделения, искал в тексте команды упоминания самого инструмента — пути к тестам, имена скриптов. Он поймал четырнадцать записей и пропустил все сорок пять фикстур: они же выглядят как обычные опасные команды, ничего «тестового» в тексте нет. Отсеклись только когда я сверил журнал напрямую со списками из run_tests.py. Не сверь я — писал бы сейчас статью с цифрой, завышенной в шестьдесят раз.
14 записей — ложные срабатывания. Про них ниже, это самое интересное.
Одна запись — настоящая.
Одна за 853 команды. 0,12%.
Пятнадцатое августа
ssh -i ~/.ssh/... root@... "sudo -u postgres psql -c 'DROP DATABASE klyvo;' \ && sudo -u postgres psql -c 'DROP ROLE klyvo;'"
Заблокировано жёстко, без запроса подтверждения.
Что происходило: я подключал новый сервис к PostgreSQL и обнаружил, что база с нужным именем уже занята. Снести и создать заново выглядело очевидным решением — я был уверен, что это остатки моего же старого прототипа.
База принадлежала другому проекту, про который я успел забыть. Бэкапов у неё не было.
Агент тут ни в чём не виноват. Он выполнял ровно то, о чём я попросил, и делал это хорошо: собрал команду, добавил удаление роли, отправил одной строкой по ssh. Проблема была в скорости — между «а снесу‑ка я её» и необратимым действием не оставалось паузы, в которую влезает мысль «стоп, а чья это база вообще».
Вот на что такая защита годится: она возвращает эту паузу. Никакого противодействия взбунтовавшемуся ИИ — просто полсекунды задержки там, где их съела автоматизация.
Четырнадцать раз, когда перехватчик мешал
Теперь неприятное. У всех четырнадцати ложных срабатываний одна природа, и она смешная.
python3 - <<'PY' p="README.md"; t=open(p).read() bad = "`terraform destroy`)., а также ..."
Скрипт правит опечатку в документации. Ничего не удаляет. Но в тексте команды есть слова terraform destroy, а движок сигнатурный — он читает текст и не отличает исполнение от упоминания.
Остальные тринадцать такие же: патч, дописывающий правила в файл правил; heredoc с описанием того, что надо блокировать; удаление одноразовой тестовой базы rm -f local.db. Перехватчик срабатывал ровно тогда, когда я писал про разрушительные команды.
Обычной работе — сборкам, миграциям, деплою, правкам кода — он за шестнадцать дней не помешал ни разу.
Чинится ли это? Нет. Отличить исполнение от упоминания сигнатурами нельзя, для этого нужно понимать смысл команды, а не её текст. Из той же природы следует и обратная беда: обход тривиален. "DR" "OP TABLE" не совпадёт ни с одним паттерном. Base64 тоже. find . -name '*.db' -deleteсделает работу rm, не содержа rm.
Так что честная формулировка: это защита от спешки, а не от умысла. Против агента, который торопится и не думает о последствиях, работает. Против агента, который целенаправленно обходит проверку, — нет.
Проверка на себе: PocketOS я бы не поймал
Раз уж я начал статью с чужого инцидента, логично прогнать его через свои же правила.
curl -X POST https://backboard.railway.app/graphql/v2 \ -d '{"query":"mutation{volumeDelete(id:\"prod\")}"}'
Пропущено. Как и railway volume delete, flyctl volumes destroy, aws rds delete-db-instance.
Правила покрывали SQL, CLI баз данных, миграционные фреймворки, NoSQL, docker и kubernetes. Из облачных провайдеров — Supabase, Turso и D1, и на этом всё. Целого класса «разрушительный вызов к API хостинга» не существовало, хотя именно так и убили PocketOS.
Ровно это и показывает, откуда берутся такие правила. Их пишут по следам чужих аварий: сначала кто‑то теряет базу, потом появляется паттерн. Моя модель угроз этого класса просто не содержала — я о нём не подумал, пока не прочитал разбор. Приятного в этом мало, но притворяться, что бывает иначе, смысла нет.
Что я из этого вынес
Главный вопрос при установке такой штуки — не сколько катастроф она предотвратит, а сколько раз в день она вас дёрнет. По моим данным в обычной работе — ноль. Один перехват за 853 команды звучит скромно ровно до того момента, пока не вспомнишь, что этот один стоил бы мне чужой базы без бэкапов.
Отдельно про наблюдение без блокировок: журнал сессии сам по себе оказался полезнее, чем я ожидал. На вопрос «что вообще произошло за эти три часа» иначе ответить нечем — контекст сессии не сохраняется, а история shell не покажет, что агент читал и правил.
И две вещи, которые важнее перехватчика. Первая: в истории PocketOS фатальной была не мутация, а бэкапы в удаляемом томе; никакая проверка команд этого не компенсирует. Вторая: посмотрите, что именно откатывает ваш инструмент. Если больше половины действий агента — bash, а откат работает по файлам, он закрывает меньшую часть происходящего, и знать об этом лучше заранее.
Журнал собран моим инструментом — Klyvo, хук перед выполнением команды, локально, без сетевых вызовов, MIT. Правила по хостинг‑провайдерам, которых не хватило в разборе выше, я добавил уже после.
Но интереснее мне другое. У кого‑нибудь есть похожие журналы? Я искал и нашёл только пересказы инцидентов, а измерений — ни одного. Если вы что‑то подобное вели, поделитесь цифрами, особенно долей ложных срабатываний: одна выборка от одного человека — это не данные, это анекдот с таблицей.

