# Вызовы для просмотра и мутации: как сделать AI-агентов безопаснее

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

Звучит очевидно, пока не сталкиваешься с настоящим API. Endpoint, который якобы предназначен только для чтения, обновляет кэш. Предварительный просмотр выделяет удалённую задачу. Endpoint обновления принимает пустой фильтр и затрагивает все записи. Команда SSH выглядит безобидно, пока подстановка оболочки не превращает узкий путь в широкий. Имена методов и добрые намерения не защищают рабочий аккаунт.

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

## Для просмотра нужна другая форма разрешений

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

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

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

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

Полезная классификация задаёт вопрос: что удалённая система сможет наблюдать после завершения запроса?

1. Вызов для просмотра возвращает сведения и не меняет значимое деловое, операционное или платёжное состояние.
2. Вызов мутации создаёт, обновляет, удаляет, выполняет, публикует, отправляет или иным образом меняет состояние, видимое извне.
3. Неоднозначный вызов нужно считать мутацией, пока кто-то не докажет обратное.

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

## HTTP-методы дают подсказку, но не решают вопрос о разрешениях

Названия HTTP-методов помогают классифицировать вызовы, но не заменяют проверку endpoint. RFC 9110 определяет GET, HEAD, OPTIONS и TRACE как «безопасные» методы, то есть клиент не просит изменить состояние. При этом RFC предупреждает, что сервер всё равно может записать запрос в журнал, списать плату с аккаунта или вызвать другие побочные эффекты.

Эту разницу легко упустить. Безопасность HTTP описывает предполагаемую семантику запроса, а не криптографическую гарантию того, что реализация ничего не сделала. RFC 9110 возлагает на владельца ресурса ответственность за отсутствие опасных действий при использовании безопасных методов. Если сервис нарушает это обещание, агент не может его обеспечить.

Считайте следующие правила отправной точкой, а не окончательным решением:

1. GET и HEAD обычно подходят для набора кандидатов на просмотр. Сначала проверьте параметры запроса и документацию endpoint.
2. POST, PUT, PATCH и DELETE относятся к мутациям, если только конкретный endpoint не имеет документированного и проверенного поведения для чтения.
3. OPTIONS может показывать возможности сервера, но некоторые платформы включают сведения, зависящие от аккаунта, поэтому область доступа всё равно нужно ограничивать.
4. Тест webhook, предварительный просмотр задачи, экспорт отчёта или endpoint поиска могут использовать POST и при этом оставаться наблюдательными. Проверьте их, вместо того чтобы разрешать весь POST.

Встречается и обратная ошибка. Разработчики иногда связывают ссылку или GET-маршрут с действием, потому что так удобнее. URL вроде `/reports/monthly?refresh=true` может заново построить дорогой кэш отчёта. GET endpoint с `?send=true` может отправить уведомление. Агент будет следовать описанию API. Не рассчитывайте, что модель заметит, как разработчик сервера проигнорировал семантику HTTP.

В документации endpoint ищите слова вроде «создаёт», «запускает», «обновляет», «генерирует», «отправляет», «записывает», «синхронизирует» и «списывает». Такие глаголы должны вывести вызов из категории просмотра, даже если маршрут использует GET. Затем протестируйте endpoint в одноразовом аккаунте и сравните состояние до и после, включая очереди задач, уведомления, счётчики использования и аудиторские записи.

Ответ HTTP тоже помогает классифицировать запрос. Ответ с идентификатором задачи, операции или URL нового ресурса часто означает, что удалённый сервис начал работу. Статус 200 доказывает только, что сервер обработал запрос. Он не доказывает, что запрос был наблюдательным.

## Определите ресурсы и эффекты до создания списков разрешений

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

Для каждого внешнего действия используйте запись вроде этой. Названия не важны, важны доказательства.

```text
Action: repository pull request list
Channel: HTTP
Target pattern: GET /repos/{owner}/{repo}/pulls
Class: discovery
Data returned: title, status, branch names, review metadata
Side-effect evidence: API reference defines this endpoint as a list operation
Scope limit: named repositories only
Review date: 2025-02-14

Action: repository merge pull request
Channel: HTTP
Target pattern: PUT /repos/{owner}/{repo}/pulls/{number}/merge
Class: mutation
Effect: changes merge state and source history
Required control: explicit approval for each call
```

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

Указывайте область действия в записи. `GET /projects` не является полезным описанием разрешения, если учётные данные позволяют перечислить каждый проект, когда-либо созданный компанией. Более точное описание называет организацию, репозиторий, пространство имён, аккаунт или путь, которые нужны агенту. Если внешний сервис не умеет так ограничивать учётные данные, установите список разрешённых целей на шлюзе действий или не делайте этот просмотр автоматическим.

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

## Запрос на чтение тоже может навредить системе

У доступа на чтение есть радиус поражения, поэтому ему нужны собственные ограничения. Агент, которому разрешено просматривать все репозитории, имена секретов, заметки об инцидентах, записи клиентов и события развёртывания, получает достаточно контекста, чтобы причинить ущерб, если злоумышленник контролирует процесс агента или его инструкции.

Популярная рекомендация звучит так: «Сначала разрешите только чтение». Она популярна, потому что кажется осторожной и совпадает с привычными названиями ролей IAM. Но она ошибочна, если широкая роль для чтения заменяет анализ классификации данных. Широкий просмотр часто создаёт крупнейшую утечку информации во всей архитектуре.

Разделяйте два вопроса:

- Может ли запрос изменить удалённое состояние?
- Может ли ответ раскрыть сведения, которые агенту нельзя получать?

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

Для чувствительных ресурсов возвращайте только нужные агенту поля. Для диагностики развёртывания могут понадобиться состояние pod, дайджест образа и последние сообщения событий. Значения переменных окружения и полный объект конфигурации не нужны. Агенту для сортировки задач могут понадобиться метки и временные отметки, но не все частные комментарии. Если API не умеет выбирать поля, поставьте перед ним узкий промежуточный action или оставьте операцию под подтверждением.

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

К endpoint поиска нужен такой же подход. Полнотекстовый поиск по исходному коду или обращениям в поддержку может раскрыть больше, чем прямой поиск объекта, а текст запроса может повлиять на поисковую строку. Ограничьте доступные коллекции и синтаксис запроса. Не передавайте созданные агентом поисковые выражения напрямую в мощную серверную систему только потому, что запрос использует GET.

## Учётные данные должны по возможности закреплять это разделение

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

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

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

Чистая схема часто включает три класса учётных данных:

1. Учётные данные для просмотра с доступом к точным ресурсам, которые агент может исследовать.
2. Учётные данные для мутаций, ограниченных необходимым набором изменений и всё равно требующих решения об одобрении.
3. Экстренные учётные данные, которыми агенты никогда не пользуются, а люди получают только в рамках процедуры реагирования на инцидент.

Не позволяйте учётным данным для мутаций по умолчанию обслуживать вызовы просмотра. Это кажется безвредным, поскольку endpoint доступен только для чтения, но усложняет будущую проверку. Запись о том, что мощные учётные данные запросили ресурс, не объясняет, действительно ли агенту нужны были такие полномочия. Делайте идентичность осмысленной.

Sallyport хранит API- и SSH-учётные данные в зашифрованном хранилище и сам выполняет внешнее действие, поэтому MCP-агент получает результат, а не секрет. Благодаря этому можно открыть узкий маршрут для просмотра, не помещая bearer-токен или закрытый SSH-материал в контекст агента.

## Подтверждение должно описывать изменение, а не наказывать за каждый запрос

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

Используйте авторизацию сессии, чтобы определить, какой процесс агента может использовать набор для просмотра. Затем требуйте отдельного подтверждения мутаций и показывайте запрос в форме, понятной человеку. «POST /v1/jobs» плохо подходит для подтверждения. «Запустить экспорт данных для проекта northwind в архивное хранилище, примерный срок 30 дней» даёт проверяющему предмет для оценки.

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

Запрос ограниченного обновления может выглядеть так:

```json
{
  "action": "update_issue",
  "target": {
    "repository": "payments-api",
    "issue": 1842
  },
  "changes": {
    "labels_add": ["needs-review"],
    "assignee": "release-manager"
  },
  "reason": "The release checklist is complete."
}
```

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

Избегайте политик одобрения, основанных только на намерении, выраженном естественным языком. «Одобрять изменения развёртывания» звучит разумно, пока агент не назовёт удаление базы данных изменением развёртывания. Привязывайте одобрение к классу операции, области цели и учётным данным. Текст помогает человеку понять запрос. Типизированные границы действий не дают этим понятиям расходиться.

## Удаление и внешнее выполнение требуют отдельного класса

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

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

Требуйте отдельный контроль каждого вызова для таких действий:

1. Удаление, очистка, архивирование, если архив ограничивает доступность, и массовое удаление.
2. Изменение разрешений, участников, ролей, секретов, учётных данных и политик доступа.
3. Развёртывание, перезапуск, масштабирование, миграция и выполнение удалённых команд.
4. Отправка писем, публикация сообщений, создание внешних задач и запуск платных операций.

До передачи запроса человеку заставьте агента разрешить идентификаторы и подготовить предварительный просмотр. Запрос удаления к `records?filter=status=inactive` должен показать точное количество, фильтр и примеры имён. Ещё лучше сначала получить идентификаторы кандидатов, а затем отправить список, который шлюз сравнит с запросом выполнения. Это не устраняет гонки, но выявляет распространённую ошибку: фильтр означает не то, что предполагал агент.

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

## SSH требует классификации на уровне команды

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

Начинайте с явных команд и аргументов. `git status --short`, `git log -n 20 --oneline` и `kubectl get pods -n staging` могут быть действиями для просмотра, если ограничить рабочий каталог, контекст кластера и пространство имён. `git push`, `kubectl apply`, `kubectl delete`, `rm`, установка пакетов и перезапуск сервисов относятся к мутациям или опасному выполнению.

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

```sh
find "$WORKDIR" -maxdepth 2 -type f -name '*.log' -print; $EXTRA_COMMAND
```

Даже если во время теста агент передаёт пустой `EXTRA_COMMAND`, позднее это значение может выполнить всё, что разрешено SSH-идентичности. Не одобряйте грамматику команд, содержащую `;`, `&&`, `||`, подстановку команд, перенаправления, раскрытие подстановочных знаков в неконтролируемых путях или запуск интерпретатора, если только само действие не проверяется как выполнение.

Используйте структурированные аргументы. Шлюз может принять действие с именем `list_recent_logs`, проверить фиксированный каталог и числовой лимит, а затем сам собрать удалённую команду. Агенту не следует отправлять строку оболочки, если можно использовать типизированное действие.

То же относится к инструментам с глаголами чтения. `kubectl get` может раскрыть секреты, если ресурс и пространство имён заданы слишком широко. `git show` может показать случайно добавленные в репозиторий учётные данные. Создавайте разрешения для команд с учётом исполняемого файла, подкоманды, аргументов, рабочего каталога и удалённой идентичности. Один глагол почти ничего не говорит.

## Аудиторские записи должны показывать, что граница сработала

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

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

Для каждого действия сохраняйте как минимум следующие поля в защищённой от незаметного изменения записи:

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

Цепочка хешей помогает обнаружить изменение журнала, но не делает расплывчатое событие полезным. Сохраняйте каноническую запись действия до выполнения, а затем связывайте с ней запись ответа. Если запись содержит только `POST /jobs`, последующий проверяющий всё равно не поймёт, запустил ли агент безобидный отчёт или дорогой экспорт.

Sallyport собирает журналы сессий и вызовов проектов в одном зашифрованном аудиторском журнале с цепочкой хешей, а `sp audit verify` проверяет эту цепочку автономно, без доступа к хранилищу. Проверка полезна только тогда, когда таксономия действий делает событие понятным, поэтому держите классификацию рядом с путём выполнения, а не добавляйте метки позже в отдельном слое отчётности.

## Проверяйте границу отказами, а не удачными сценариями

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

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

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

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

```text
09:14:03  discovery  GET /projects/acme/services?limit=20  allowed
09:14:05  discovery  GET /projects/acme/services/api-7/events  allowed
09:14:11  mutation   POST /projects/acme/services/api-7/restart  approval required
09:14:32  mutation   POST /projects/acme/services/api-7/restart  approved by operator
09:14:34  mutation   result: accepted, operation=op_481
```

Если в третьей строке вместо этого указано `GET /services/api-7?action=restart`, модель классификации уже обнаружила дефект. Исправьте контракт действия или оставьте этот маршрут под контролем мутаций. Не создавайте исключение только потому, что endpoint неудобен.

Начните с внешних действий, которые ваши агенты уже выполняют. Отметьте каждое по эффекту, проверьте реальное поведение, ограничьте цель и протестируйте сценарии отказа. Когда агент просит сделать что-то новое, требуйте явной классификации до того, как действие попадёт в рабочую среду. Такая небольшая пауза дешевле попыток объяснить непроверенное изменение постфактум.
