Безопасность MCP: OAuth, права tools и prompt injection
Что такое безопасность MCP?
Безопасность MCP — набор контролей авторизации, изоляции, валидации, подтверждения и аудита, которые ограничивают доступ Model Context Protocol клиентов и серверов к данным и действиям. Она охватывает remote OAuth-серверы и локальные процессы с правами пользователя.
TL;DR
- -Безопасность MCP — это безопасность capabilities: важно не только кто вошёл, но и что сможет сделать обманутая модель, клиент, сервер или tool result
- -Remote HTTP-сервер должен на каждом запросе проверять issuer, audience, срок и scopes токена; MCP-токен клиента нельзя передавать в upstream API
- -Authorization и подтверждение tool call — разные контроли: scopes задают допустимые возможности, approval решает, нужно ли выполнять конкретное действие сейчас
- -Tool descriptions, arguments и results считаются недоверенными; policy нужно исполнять детерминированным кодом, а не поручать модели
- -Локальному серверу выдаются только нужные filesystem, network и secrets; audit должен помогать расследованию без сохранения credentials и лишних PII
MCP-сервер превращает вывод модели в capability. Поиск события в календаре почти безопасен. Отправка письма, SQL-запрос, изменение cloud infrastructure или чтение домашней директории — уже нет. Поэтому главный вопрос звучит не «безопасен ли этот prompt?», а так:
Если один компонент обманут или скомпрометирован, что он сможет прочитать, изменить или отправить?
Такое framing находит проблемы, которые OAuth не решает. Валидного пользователя можно склонить к неверному подтверждению. Доверенный сервер может вернуть отравленный контент. Легитимный локальный сервер может получить неоправданно широкие права.
Это security-слой для общего разбора архитектуры MCP и MCP-серверов в production. Материал опирается на актуальную спецификацию от 28 июля 2026 года; ссылки ведут на эту версию, а не на пересказ без даты.
Сначала нарисуйте trust boundaries
Обычный remote path включает четыре principals:
user → MCP host/client → MCP server → upstream API or database
↘ model context and tool results
У них нет общей identity и единого уровня доверия.
- Host решает, какой context и tools увидит модель.
- Client говорит по MCP и передаёт authorization текущего caller.
- Server проверяет caller и исполняет tool policy.
- Upstream system использует собственную identity и модель прав.
- Model предлагает действия, но не является authorization engine.
Зафиксируйте, на какой границе проверяются identity, tenant, ownership объекта, scope и user approval. Если в таблице написано «это контролирует prompt», жёсткого контроля там нет.
Threat model: какие сбои действительно проектировать
| Сбой | Во что он превращается | Основной контроль |
|---|---|---|
| Вредоносный tool description | Модель выбирает чужой tool или раскрывает context | Trust registry, review, allowlist, раздельные contexts |
| Injection в документе или tool result | Read-операция управляет последующей записью | Считать result данными, разделить read и act, подтверждать опасные calls |
| Широкие scopes или wildcard tools | Один токен открывает несвязанные системы | Per-capability scopes и step-up authorization |
| Token passthrough | Токен повторно используется не тем сервисом | Audience binding и отдельные upstream credentials |
| Непроверенные arguments | SQL, command, path, URL или tenant injection | Schema, semantic validation и server-side policy |
| Угадываемый workflow handle | Доступ к состоянию другого пользователя | Случайный expiring handle, привязанный к principal |
| SSRF через OAuth metadata | Доступ к внутренним сервисам или cloud metadata | HTTPS, redirect validation и egress policy |
| Слишком мощный local server | Чтение source, SSH keys или credentials | Sandbox и явные filesystem/network grants |
| Подробная telemetry | Credentials или customer data остаются в logs | Field allowlist, redaction, access control, retention |
«Модель может hallucinate» — не threat model. Полезная угроза связывает сбой с capability и конкретным ущербом.
Remote authorization: защищаем resource-server boundary
Для HTTP transports MCP задаёт OAuth-based flow. Актуальная authorization specification требует Protected Resource Metadata для поиска authorization server и Resource Indicators, чтобы токен был привязан к нужному MCP server.
Каждый защищённый запрос проходит детерминированную цепочку:
signature → issuer → audience/resource → expiry → scopes → tenant/object policy
Первые пять проверок валидируют credential. Последняя — само действие. Scope
tickets:write не доказывает, что caller может менять любой ticket в любом tenant.
Привязывайте scopes к capabilities
Не заводите один mcp:* или admin. Используйте стабильные и узкие permissions:
issues:read
issues:comment
issues:close
deployments:read
deployments:promote
Начинайте с discovery и read access. Более сильный scope запрашивайте при первой
привилегированной операции. Текущая спецификация поддерживает это через 403 и
insufficient_scope. В официальном разделе
scope minimization
объясняется, почему выдача всех прав заранее увеличивает blast radius и обесценивает
consent.
Не передавайте MCP-токен дальше
Token passthrough — не shortcut, а удаление границы между MCP server и API за ним. Спецификация требует принимать только токены с audience этого MCP server и запрещает пересылать чужие токены downstream.
Если сервер вызывает GitHub, Google или внутренний API, используйте отдельный upstream token:
client --token audience:mcp.example--> MCP server
MCP server --different upstream token--> API
Mapping хранится на сервере. Ни один токен не должен попадать в model context, tool result, query string или штатные logs.
OAuth discovery тоже делает исходящие запросы
Вредоносный сервер может положить внутренний адрес в resource или authorization metadata. Валидируйте каждый discovery URL и каждый redirect hop. В production следует требовать HTTPS, запрещать private, loopback, link-local и cloud metadata ranges, кроме явного локального исключения, и повторять проверку после DNS resolution. Egress proxy надёжнее самописного списка строк.
Официальный раздел про SSRF разбирает DNS rebinding и redirect chains, которые пропускает наивная URL validation.
Authorization не заменяет подтверждение tool call
Authorization отвечает: может ли principal использовать capability? Approval: нужно ли сейчас выполнить это действие с этими аргументами? Часто нужны оба слоя.
| Операция | Default handling |
|---|---|
| Чтение из узкого доверенного источника | Разрешить после обычной authorization |
| Web, email или uploaded files | Разрешить чтение, пометить result как untrusted |
| Создание обратимого draft | Показать target и важные fields |
| Send, publish, charge, delete, deploy, смена access | Запросить явное подтверждение |
| Arbitrary code или query | Запретить по умолчанию; дать узкую операцию |
MCP tools specification рекомендует оставлять человеку возможность отклонить call и показывать inputs чувствительной операции. Tool annotations помогают UI, но не исполняют policy. Согласно спецификации tools, клиент обязан считать annotations недоверенными, пока не доверяет самому серверу.
Approval должен показывать последствие, а не непрозрачный JSON. «Отправить сообщение
[email protected]» понятно. «Запустить messages.execute?» — нет. Привяжите
подтверждение к canonical form аргументов, чтобы target нельзя было подменить между
preview и execution.
Любой model-facing content считается недоверенным
Prompt injection часто приходит косвенно: загруженная страница, issue description, email, комментарий в source или MCP tool result предлагает модели забыть исходную задачу и выполнить другую. Источником может быть операционно доверенный сервер, который читает недоверенные данные.
Не оставляйте system prompt единственной защитой. Практические контроли стоят вокруг модели:
- Разделяйте чтение и действие. Компонент, который суммирует произвольный web, не должен одновременно владеть tools для deploy или отправки сообщений.
- Передавайте дальше структурированные факты. Преобразуйте текст в typed record с provenance. Не переносите скрытый HTML, инструкции и лишний raw content.
- Авторизуйте каждое действие на сервере. Убедительный argument не даёт модели новых прав.
- Подтверждайте последствия. Показывайте destination, mutation и источник запроса.
- Ограничивайте egress. Tool для одного API не нужен произвольный интернет.
- Проверяйте rendered output. Удаляйте опасный HTML и не загружайте автоматически model-generated URLs с приватными данными.
OWASP Prompt Injection Prevention Cheat Sheet рассматривает least privilege, структурное разделение, output validation и human approval как defense in depth. Универсального детектора среди них нет. Проектируйте так, будто одна injection однажды пройдёт, и ограничивайте её последствия.
Делайте узкие tools, а не remote shell
Лучшая validation начинается с маленького интерфейса. Сравните:
execute_sql(query: string)
и:
get_invoice(invoice_id: InvoiceId)
mark_invoice_reviewed(invoice_id: InvoiceId, reason: ReviewReason)
Во втором варианте сервер может проверить ownership, допустимые state transitions и ограниченные values. JSON Schema ловит ошибки формы, но не знает, находится ли path внутри разрешённого root и принадлежит ли object caller.
Для каждого tool проверяйте:
- types, lengths, formats, enums и request size;
- tenant и ownership по verified identity, а не по словам модели;
- paths после canonicalization и только внутри allowed root;
- URLs после parsing и DNS resolution, с egress policy;
- query parameters через prepared statements или фиксированный query builder;
- business transitions и idempotency keys;
- result size, content type и fields, возвращаемые модели.
Разделяйте read и write tools. Ставьте rate limit по principal и дорогой операции. У каждого upstream call должны быть deadline и cancellation. Эти же решения упрощают тестирование и эксплуатацию.
Local server нужна process security, а не имитация OAuth
Актуальная MCP authorization spec рекомендует stdio implementations получать credentials из environment, а не использовать HTTP authorization flow. Безопасным локальный server от этого не становится: он работает с теми правами, которые дал host.
Перед установкой:
- проверьте точную command, arguments, источник package и pinned version;
- не запускайте mutable package install при каждом старте;
- выдайте только нужные directories, по возможности read-only;
- закройте network access, кроме необходимых named destinations;
- передайте только те secrets, которыми пользуется этот server;
- risky parsers и code execution запускайте в sandbox или container;
- оставьте stdio, если другим локальным процессам не нужен HTTP-доступ.
Официальный раздел про local MCP servers говорит прямо: server может исполняться с правами клиента. Удобная кнопка установки — это consent на code execution, а не безобидная настройка.
State handle — имя, а не credential
Длинный workflow часто возвращает workflow_id, basket_id или другой handle для
следующего call. На этом уровне протокол июля 2026 года stateless. Одного знания handle
не должно хватать для доступа к состоянию.
Генерируйте opaque и неугадываемые handles, ограничивайте lifetime и привязывайте на
сервере к verified user и tenant. Проверяйте binding на каждом call. Не доверяйте
user_id, который модель передала рядом с handle.
Audit без новой утечки данных
Полезный audit event отвечает: кто попытался сделать что, с каким объектом, по какой policy и с каким результатом:
{
"event": "mcp.tool_call",
"request_id": "req_01...",
"principal_id": "usr_internal_42",
"server": "billing-tools",
"tool": "mark_invoice_reviewed",
"target_id": "inv_7f...",
"decision": "allowed",
"approval_id": "apr_01...",
"scope_set": ["invoices:review"],
"result": "success",
"duration_ms": 184
}
Не логируйте bearer tokens, authorization codes, cookies, secret arguments, полные email bodies и raw model context по умолчанию. Используйте internal IDs и field allowlists. Отделите audit storage от записываемого сервиса, ограничьте доступ, задайте retention и проверьте deletion, если этого требует policy.
Security tests, которые должны быть в CI
Happy path через Inspector — не security test. Добавьте случаи:
- missing, expired, wrong-issuer и wrong-audience tokens;
- valid token с недостаточным scope;
- cross-tenant object IDs и украденные state handles;
- лишние JSON properties, oversized input, path traversal и injection strings;
- URLs, ведущие на loopback, private, link-local или redirected internal targets;
- tool output с indirect instructions и exfiltration links;
- изменение arguments после approval preview;
- duplicate writes, retries, timeouts и cancellation;
- seeded fake secrets в logs и traces;
- отказ authorization, policy, audit и upstream services.
После этого запускайте end-to-end agent scenarios. Полный гайд по тестированию MCP-сервера разбирает contract snapshots, protocol-version matrix, transport failure injection и conformance. Эти тесты доказывают, что сервер отклоняет плохой запрос, но не гарантируют безопасную последовательность tools. Гайд по evaluation AI-агентов разбирает trajectory и task-level regression gates.
Checklist перед production
- У каждой trust boundary и категории данных есть owner.
- Remote token проверяется по issuer, audience, expiry и scope.
- Downstream API использует отдельный credential; token passthrough невозможен.
- Tool access выводится из verified identity и policy.
- Sensitive call требует argument-bound user approval.
- Tools задают узкие операции и отклоняют неизвестные fields.
- External content и tool results считаются untrusted data.
- Local process имеет явные filesystem, network и secret grants.
- Handles непрозрачны, истекают и привязаны к caller.
- Настроены rate limits, deadlines, output limits и cancellation.
- Audit events не содержат secrets и лишние personal data.
- Проверены revocation и emergency disable path.
Ни один пункт не делает агента «защищённым от prompt injection». Вместе они превращают сбой модели из неограниченного инцидента в отклонённый запрос, ограниченное действие или approval, на котором пользователь может остановиться.