Безопасность 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 или раскрывает contextTrust registry, review, allowlist, раздельные contexts
Injection в документе или tool resultRead-операция управляет последующей записьюСчитать result данными, разделить read и act, подтверждать опасные calls
Широкие scopes или wildcard toolsОдин токен открывает несвязанные системыPer-capability scopes и step-up authorization
Token passthroughТокен повторно используется не тем сервисомAudience binding и отдельные upstream credentials
Непроверенные argumentsSQL, command, path, URL или tenant injectionSchema, semantic validation и server-side policy
Угадываемый workflow handleДоступ к состоянию другого пользователяСлучайный expiring handle, привязанный к principal
SSRF через OAuth metadataДоступ к внутренним сервисам или cloud metadataHTTPS, redirect validation и egress policy
Слишком мощный local serverЧтение source, SSH keys или credentialsSandbox и явные filesystem/network grants
Подробная telemetryCredentials или customer data остаются в logsField 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 единственной защитой. Практические контроли стоят вокруг модели:

  1. Разделяйте чтение и действие. Компонент, который суммирует произвольный web, не должен одновременно владеть tools для deploy или отправки сообщений.
  2. Передавайте дальше структурированные факты. Преобразуйте текст в typed record с provenance. Не переносите скрытый HTML, инструкции и лишний raw content.
  3. Авторизуйте каждое действие на сервере. Убедительный argument не даёт модели новых прав.
  4. Подтверждайте последствия. Показывайте destination, mutation и источник запроса.
  5. Ограничивайте egress. Tool для одного API не нужен произвольный интернет.
  6. Проверяйте 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, на котором пользователь может остановиться.

Часто задаваемые вопросы

Делает ли OAuth MCP-сервер безопасным?
Нет. OAuth аутентифицирует и авторизует caller на транспортной границе. Сервер всё равно обязан проверять права на конкретные объекты, валидировать tool arguments, защищать downstream credentials и ограничивать side effects. Клиент должен считать контент сервера недоверенным и запрашивать подтверждение чувствительных операций.
Нужен ли OAuth локальному stdio MCP-серверу?
Обычно нет. Актуальная MCP authorization specification относится к HTTP transports и рекомендует stdio получать credentials из environment. Но локальному серверу всё равно нужны process isolation, узкий доступ к файлам и сети, безопасная передача secrets и явное согласие на установку.
Могут ли tool annotations заменить authorization policy?
Нет. Аннотации read-only или destructive — подсказки, которые сообщает сам сервер. MCP-клиент обязан считать их недоверенными, если не доверяет серверу. Реальные access и approval rules исполняются вне модели и не зависят от самоописания tool.
Можно ли закрыть prompt injection более строгим system prompt?
Нет. System instructions задают поведение, но не создают жёсткую security boundary. Последствия снижаются через least privilege, изоляцию, детерминированную проверку аргументов и outputs, подтверждение действий и adversarial tests.