Технологии

Почему нельзя просто дать агенту доступ к терминалу

Инструмент выполнения команд в Persona существует и помечен как опасный. Разбираем четыре сценария ущерба и список ограничений, без которых его включать нельзя.

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

Именно универсальность и есть проблема. Инструмент, который может сделать что угодно, невозможно ограничить по смыслу: у него нет параметров, которые можно проверить. Есть строка. И либо вы её выполняете, либо нет.

Начнём с честного признания, потому что дальше речь пойдёт о рисках, которые есть и у нас. В Persona инструмент выполнения команд существует. Он называется run_shell, лежит в app/mcp/builtin_tools.py, отнесён к классу риска «выполнение» в app/mcp/tool_policy.py и по умолчанию выключен. Ниже — почему он выключен и что должно быть сделано до того, как его включать.

#Короткий ответ

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

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

#Как это устроено и где ломается

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

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

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

Единственное, на что фильтр по подстрокам годится, — снизить частоту случайного срабатывания на самых известных формулировках. Строить на нём политику безопасности нельзя.

#Четыре сценария ущерба

Сценарии расставлены по возрастанию неприятности и по убыванию очевидности.

#1. Слишком широко понятая уборка

Вы просите: «убери из проекта временные файлы». Агент пишет команду удаления по маске. Маска оказывается шире, чем вы имели в виду, потому что модель не знает, какие из ваших файлов временные, а какие просто называются похоже.

Ущерб: потерянные файлы. Заметность: высокая, но обычно постфактум. Обратимость: зависит от того, есть ли у вас бэкап; корзину командное удаление, как правило, минует.

#2. Команда, пришедшая из чужого текста

Агент открыл веб-страницу, прочитал чужой файл или получил сообщение в групповом чате. В тексте — инструкция, адресованная модели. Модель воспринимает её как часть задачи и выполняет.

Это не гипотетический класс: механика разобрана в справке про промпт-инъекцию. Особенность именно терминала в том, что здесь инъекция превращается не в неправильный ответ, а в выполненную команду.

Ущерб: произвольный. Заметность: низкая — команда выполнилась внутри длинного хода, среди других вызовов. Обратимость: никакой гарантии.

#3. Утечка через сеть

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

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

#4. Тихая порча состояния

Команда отработала наполовину: часть файлов переписана, часть нет, процесс убит по таймауту через тридцать секунд. Агент видит обрыв и не знает, что успело выполниться. Вы видите «команда превысила время» и тоже не знаете.

Ущерб: система в промежуточном состоянии. Заметность: отложенная — обычно выясняется через день. Обратимость: требует ручного разбора.

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

#Что из этого следует на практике

Если терминал всё-таки нужен — а он бывает нужен, — включать его следует после того, как выполнены все пункты ниже. Не «желательно», а именно все.

  1. Отдельная учётная запись или контейнер. Команда не должна выполняться от пользователя, который имеет доступ к вашей почте, ключам и документам. Это единственное ограничение из списка, которое работает против всех четырёх сценариев сразу.
  2. Одноразовое подтверждение на каждый вызов. Человек видит команду целиком до выполнения. Не «разрешить на сессию». В Persona вызовы класса «выполнение» и «разрушение», пришедшие из Telegram, паркуются за кнопкой подтверждения, а имя инструмента и аргументы читаются из строки в базе, а не из нажатия.
  3. Запрет на фоновых поверхностях. Цепочка, работающая без человека у экрана, не имеет права выполнять команды: подтверждать некому. Если канала подтверждения нет — отказ, а не выполнение.
  4. Запрет в групповых чатах. Там пишут посторонние. В Persona в групповом чате доступен ровно один инструмент, и это поиск в сети.
  5. Сетевые ограничения на уровне системы. Если исходящие соединения из-под этой учётной записи ограничены, третий сценарий закрывается на уровне, который команда не обходит.
  6. Журнал всех выполненных команд отдельно от чата. Не в истории диалога, которую можно потерять или переписать, а в отдельном журнале с временем и результатом.
  7. Бэкап, проверенный восстановлением. Не «есть», а «я восстанавливался из него в этом месяце».
  8. Таймаут и предел объёма вывода. У нас это тридцать секунд и обрезка вывода; предел нужен не ради безопасности, а чтобы одна команда не съела контекст целиком.

Если хотя бы один пункт не выполнен — терминал остаётся выключенным. Общая рамка того, как раздаются права остальным инструментам, — в руководстве по белым спискам и правам по умолчанию.

#Чем заменить

В подавляющем большинстве случаев терминал просят не потому, что нужен терминал, а потому, что нужно конкретное действие. Замена почти всегда существует и почти всегда лучше.

Зачем просят терминал Чем заменить
Читать и писать файлы Файловые инструменты с белым списком папок
Искать по содержимому Отдельный инструмент поиска по коду или тексту
Смотреть состояние репозитория Инструмент только на чтение: статус, лог, diff
Запускать тесты Отдельный инструмент с фиксированной командой
Ставить пакеты Не автоматизировать вовсе

Разница принципиальная: у каждого из заменителей есть проверяемые параметры. Путь можно сравнить с белым списком, запрос поиска безвреден по построению, фиксированная команда запуска тестов не принимает произвольную строку. Как настраивается первый из них — в руководстве по файловому доступу для агента.

#Где интуиция подводит

«Я запрещу опасные команды списком». Список подстрок ловит опечатки, а не намерение. Обходится тривиально и создаёт ложное чувство защищённости, которое хуже отсутствия защиты: с фильтром люди включают терминал охотнее.

«Модель не станет делать ничего разрушительного». Вопрос не в «станет», а в «поймёт ли задачу так же, как вы». Первый сценарий — не злой умысел, а разное понимание слова «временные».

«Я же вижу, что она делает». В длинном ходе с шестью раундами и десятком вызовов вы видите ленту, а не читаете каждую строку. Именно на это рассчитан второй сценарий.

«Это же локально, значит безопасно». Локально означает, что ущерб придётся вашим данным, а не чужим. Разбор того, какие угрозы остаются в полностью локальной установке, — в материале про угрозы локального ИИ, а про риски самостоятельного размещения — в честном разборе рисков самохостинга.

«Включу на один раз». Инструмент, включённый на один раз, остаётся включённым.

#Когда терминал действительно оправдан

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

Признак, отличающий этот сценарий от всех остальных: вам не жалко потерять эту среду целиком. Если жалко — терминал в ней не нужен.

#Частые вопросы

#В Persona есть инструмент выполнения команд?

Да, он есть и называется run_shell. По умолчанию он выключен, отнесён к отдельному классу риска «выполнение», не входит в набор инструментов, которые модель может вызывать автономно, и при обращении из Telegram паркуется за одноразовым подтверждением. Мы не считаем правильным делать вид, что такого инструмента у нас нет.

#Достаточно ли фильтровать опасные команды по списку?

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

#Что будет, если команда зависнет?

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

#Можно ли дать терминал только на чтение?

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

#Что делать, если агент уже что-то удалил?

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

#Терминал в контейнере — это безопасно?

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

#Вывод

Терминал — единственный инструмент агента, который нельзя ограничить по смыслу: у него нет проверяемых параметров, есть произвольная строка. Поэтому вся защита вокруг него внешняя, и её либо выстроили целиком, либо не выстроили вовсе.

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

Хотите ИИ, который помнит вас?

Persona держит контекст между чатами и работает на вашем железе — если развернуть её у себя и подключить локальную модель.

Создать аккаунт