Безопасность MCP
Прокси минимальных полномочий для MCP-инструментов
Универсальный токен превращает любой промах агента, ошибку инструмента или инъекцию в запросе в действие с максимальными правами. Исправить это можно промежуточным слоем, который разрешает только нужную операцию над заданными ресурсами и лишь на время конкретной задачи.
Что именно мы строим
Принцип минимальных полномочий требует выдавать субъекту только те права, которые необходимы для текущей операции. Для MCP этого недостаточно реализовать одним списком разрешённых инструментов: инструмент с нейтральным именем вроде update_record всё ещё может принимать произвольный идентификатор, менять защищённые поля или использовать долгоживущий токен.
Прокси располагается между MCP-сервером и целевой системой. Агент вызывает MCP-инструмент, сервер передаёт прокси краткоживущий пропуск задачи, а прокси проверяет четыре условия:
- какая операция разрешена;
- над каким ресурсом или набором ресурсов её можно выполнить;
- какие поля, объём и частота допустимы;
- не истёк ли срок и не был ли пропуск уже отозван.
Постоянные учётные данные целевой системы остаются внутри прокси. Они не попадают в контекст модели, аргументы MCP-вызова, журналы агента или окружение MCP-сервера.
1. Опишите разрешение как данные
Начните не с маршрутов API, а с контракта задачи. Ниже — пример политики, а не готовый стандарт MCP. Имена операций и ресурсов следует заменить на сущности вашей системы.
{
"task_id": "task-7f31",
"subject": "mcp:journal-editor",
"audience": "resource-proxy",
"operations": ["article.read", "article.update_draft"],
"resources": [
"article:draft-184"
],
"field_rules": {
"article.update_draft": {
"allow": ["title", "summary", "body"],
"deny": ["status", "author_id", "canonical_url"]
}
},
"limits": {
"max_requests": 20,
"max_body_bytes": 65536
},
"not_before": "2026-07-29T10:00:00Z",
"expires_at": "2026-07-29T10:10:00Z",
"nonce": "single-use-random-value"
}
Политика должна быть закрытой по умолчанию: отсутствующая операция, неизвестный ресурс или новое поле означают отказ. Не используйте правило вида article.*, если задаче нужна только правка черновика. Не полагайтесь на текстовое описание задачи как на авторизацию — оно неоднозначно и управляется моделью.
Ресурс лучше задавать стабильным внутренним идентификатором. Если разрешены ресурсы по префиксу, тегу или каталогу, прокси должен вычислить точное множество на момент выдачи пропуска либо повторно проверить принадлежность при каждом вызове. Иначе переименование или перемещение ресурса незаметно расширит доступ.
2. Разделите выдачу и применение полномочий
Архитектура состоит из трёх ролей:
- Выдающий компонент получает проверенное описание задачи и подписывает краткоживущий пропуск.
- MCP-сервер преобразует вызов инструмента в запрос к прокси, но не хранит универсальный секрет.
- Прокси применения проверяет пропуск, нормализует параметры, применяет ограничения и только затем обращается к целевой системе со своими защищёнными учётными данными.
Агент
│ MCP: article_update_draft
▼
MCP-сервер ── пропуск задачи + аргументы ──▶ Прокси
│
├─ проверка подписи и срока
├─ проверка операции и ресурса
├─ фильтрация полей и лимитов
▼
Целевая система
Выдающий компонент и прокси могут быть одним сервисом, но ключ подписи и постоянные секреты не должны передаваться MCP-процессу. Для изоляции полезно применять отдельную сетевую политику: MCP-серверу разрешён выход только к прокси, а прямой адрес целевой системы для него недоступен.
3. Сделайте пропуск короткоживущим и привязанным к назначению
Пропуск может быть подписанным компактным объектом или случайным непрозрачным идентификатором, который прокси сопоставляет с серверной записью. Второй вариант упрощает немедленный отзыв и скрывает детали политики от MCP-сервера. В обоих случаях проверяйте:
- подпись или существование серверной сессии;
audience, чтобы пропуск нельзя было применить в другом сервисе;- точное серверное время
not_beforeиexpires_atс небольшим допустимым рассогласованием часов; - идентификатор задачи и субъекта;
- состояние отзыва, счётчик запросов и одноразовый
nonce, если операция не должна повторяться.
Время жизни выбирается по длительности шага, а не сессии агента. Для десятиминутной операции не нужен токен на сутки. Продление должно быть новой выдачей после повторной проверки состояния задачи, а не автоматическим обновлением старого пропуска.
Пример безопасной передачи пропуска в локальной проверке:
read -rsp "Task capability: " TASK_CAP
printf '\n'
curl --fail-with-body --silent --show-error \
--request POST \
--header "Authorization: Bearer ${TASK_CAP}" \
--header "Content-Type: application/json" \
--data '{"article_id":"draft-184","patch":{"summary":"Проверочный текст"}}' \
"https://proxy.internal/v1/article/update-draft"
unset TASK_CAP
Команда намеренно не содержит реального адреса, секрета или клиентских данных. Не помещайте пропуск в URL, историю оболочки, файл конфигурации проекта или диагностический вывод.
4. Авторизуйте семантическую операцию, а не HTTP-метод
Разрешения GET и POST слишком грубы. Один маршрут может выполнять разные действия в зависимости от тела запроса. Сначала сопоставьте MCP-инструмент с фиксированной операцией, затем соберите исходящий запрос из проверенных полей.
# Пример декларативного правила прокси
operation: article.update_draft
match:
method: POST
path: /v1/article/update-draft
input:
required:
- article_id
- patch
resource_from: article_id
allow_patch_fields:
- title
- summary
- body
deny_unknown_fields: true
constraints:
body_max_bytes: 65536
requests_per_capability: 20
upstream:
method: PATCH
path_template: /internal/articles/{article_id}
forward_client_headers: false
Прокси не должен принимать от агента произвольный upstream-URL, имя хоста, HTTP-метод или заголовок авторизации. Путь к целевой системе формируется из серверного шаблона. Идентификатор ресурса проходит синтаксическую проверку и затем сравнивается с политикой как каноническое значение.
Если инструмент работает с файловыми путями, одной проверки префикса недостаточно. Нужны канонизация пути, запрет выхода через .., проверка символических ссылок и открытие файла относительно заранее разрешённого дескриптора каталога. Аналогично для URL следует запретить перенаправления на неожиданные хосты и внутренние адреса.
5. Уберите доверие к входным заголовкам
На границе прокси удаляйте заголовки, которые могут влиять на идентичность, маршрутизацию и подпись запроса. Создавайте их заново после успешной авторизации.
strip_request_headers:
- Authorization
- Cookie
- Forwarded
- X-Forwarded-For
- X-User
- X-Role
- X-Upstream-Host
- X-Signature
set_request_headers:
X-Task-ID: from_verified_capability
X-Operation: from_matched_policy
response:
expose_headers:
- Content-Type
- Request-ID
redact_fields:
- internal_token
- credential
- debug_context
Не пересылайте ответ upstream целиком. Ошибки могут содержать внутренние URL, фрагменты запросов и диагностические данные. Прокси должен возвращать агенту устойчивый код, безопасное описание и идентификатор события для серверного расследования.
6. Добавьте идемпотентность, квоты и аудит
Ограничение прав не защищает от многократного повторения разрешённого действия. Для изменяющих операций принимайте идентификатор идемпотентности, привязывайте его к задаче, операции, ресурсу и хешу нормализованного тела. Повтор с тем же ключом и другим телом должен завершаться отказом.
Счётчики применяются сервером: число запросов, суммарный объём, количество затронутых объектов и допустимая скорость. Клиентское поле limit не считается ограничением, пока прокси не уменьшит его до собственного максимума.
В журнал решения записывайте время, задачу, субъекта, операцию, ресурс, результат проверки, код причины отказа, идентификатор политики и идентификатор upstream-запроса. Не записывайте пропуск, постоянный секрет, полное тело документа или чувствительные ответы. Аудит должен отвечать на вопрос «почему действие разрешили», а не воспроизводить секреты.
Проверка результата
Проверяйте не только успешный сценарий. Основная ценность прокси проявляется в предсказуемых отказах. Выполните проверки в изолированном тестовом окружении с искусственными ресурсами:
| Запрос | Ожидаемый результат |
|---|---|
Разрешённая операция над article:draft-184 |
Успех; изменены только разрешённые поля |
| Та же операция над другим идентификатором | Отказ до обращения к upstream |
Добавлено поле status |
Отказ всего запроса, а не тихое расширение прав |
| Пропуск с истёкшим сроком | Отказ независимо от валидной подписи |
Пропуск для другой audience |
Отказ |
| Повтор одноразовой операции | Отказ либо прежний идемпотентный результат |
| Запрос сверх квоты или размера | Отказ без частичного выполнения |
Подмена Authorization для upstream |
Заголовок отброшен; используется серверная идентичность прокси |
Дополнительно остановите доступ MCP-сервера к прокси или отзовите пропуск во время выполнения. Система должна завершиться закрытым отказом, а не обходить прокси прямым запросом. Затем сопоставьте журнал прокси с журналом целевой системы по идентификатору запроса и убедитесь, что запрещённые попытки не дошли до upstream.
Критерий готовности можно сформулировать так: компрометация MCP-сервера позволяет выполнить не больше операций, чем явно записано в активных пропусках, и только над указанными ресурсами до истечения их срока.
Типовые ошибки
- Один сервисный токен, спрятанный в MCP-сервере
- Секрет скрыт от модели, но права инструмента не уменьшены. При ошибке сервер всё ещё может обратиться к любому доступному ресурсу.
- Разрешение по имени инструмента
- Имя не ограничивает аргументы. Нужны отдельные проверки операции, ресурса, полей и объёма.
- Проверка только на API-шлюзе
- Шлюз часто видит маршрут и HTTP-метод, но не понимает бизнес-смысл поля. Семантическая политика должна применяться до формирования upstream-запроса.
- Маски ресурсов без канонизации
- Разные формы идентификатора, пути или регистра способны обойти строковое сравнение. Нормализуйте значение до проверки.
- Долгое время жизни «для надёжности»
- Так временное разрешение становится запасным универсальным ключом. Лучше повторная выдача после проверки задачи.
- Тихое удаление запрещённых полей
- Агент считает действие выполненным и продолжает работу на ложной предпосылке. Для значимых полей безопаснее отклонить запрос целиком и вернуть машинно-читаемую причину.
- Секреты в аудит-логах
- Журналирование превращается в новый канал утечки. Храните решение и метаданные, но не полномочия и содержимое чувствительных объектов.
Ограничения подхода
Прокси не определяет самостоятельно, является ли цель пользователя легитимной. Если выдающий компонент сформировал чрезмерно широкую политику, прокси точно применит именно её. Поэтому создание пропуска — отдельная доверенная операция с шаблонами задач, проверкой владельца ресурса и, где необходимо, подтверждением человеком.
Семантические ограничения требуют знания домена. Поле body может содержать ссылку, команду или конфиденциальный текст; обычный allowlist полей этого не обнаружит. Для таких случаев нужны дополнительные валидаторы, модерация или разделение операции на более узкие инструменты.
Постоянные учётные данные всё ещё существуют внутри прокси. Их следует изолировать, регулярно менять и ограничить на стороне целевой системы настолько, насколько она позволяет. Прокси уменьшает поверхность доступа агента, но сам становится критическим компонентом: ему нужны обновления, наблюдаемость, резервирование и проверка конфигурации.
Наконец, не все upstream-системы поддерживают атомарность и идемпотентность. Если запрос частично выполнился до сетевой ошибки, прокси не всегда способен безопасно определить результат. Такие операции требуют серверного ключа идемпотентности, транзакционного API либо отдельного механизма сверки.
Итоговый контрольный список
- Универсальные секреты находятся только в доверенном прокси.
- MCP-сервер не имеет прямого сетевого пути к целевой системе.
- Пропуск привязан к субъекту, задаче, назначению и короткому сроку.
- Операции перечислены явно; неизвестные операции запрещены.
- Ресурсы канонизируются и сверяются с точным allowlist.
- Поля, размер, количество объектов и частота ограничены сервером.
- Клиент не управляет upstream-адресом, методом и заголовками идентичности.
- Повторы контролируются ключом идемпотентности или одноразовым nonce.
- Отзыв применяется немедленно, а продление требует новой выдачи.
- Журнал содержит основание решения, но не содержит секретов.
- Негативные сценарии подтверждают, что запрещённые запросы не достигают upstream.
Дополнительные схемы внедрения собраны в разделе руководств, а определения терминов — в глоссарии.