Что на самом деле пошло не так с MCP-серверами в 2026 году?
Один и тот же сценарий повторяется. В транспорте STDIO конфигурация, которая называет сервер, одновременно является командой его запуска, так что у кого есть право записи в конфиг, тот запускает процесс. 15 апреля 2026 года OX Security показала, что официальные MCP SDK (Python, TypeScript, Java, Rust) передают команды из конфигурации через StdioServerParameters прямо в операционную систему без очистки. Исследователи насчитали более 7 000 публично доступных серверов и оценили число уязвимых экземпляров до 200 000. Вторая цифра — оценка. Anthropic отказалась менять архитектуру: по словам OX, такое поведение назвали ожидаемым, а очистку оставили разработчикам клиентов.
Три инцидента за последние двенадцать месяцев показывают, в каких формах это проявляется:
- Поле конфигурации превращается в исполнение кода. Узел CustomMCP во Flowise выполнял JavaScript из присланной пользователем конфигурации MCP-сервера (CVE-2025-59528). В обзоре OWASP за первый квартал 2026 года зафиксирована активная эксплуатация с 7 апреля 2026 года, при 12 000–15 000 экземплярах, открытых в интернет. Исправлено в версии 3.0.6.
- Репозиторий, который сам запускает серверы. 25 февраля 2026 года Check Point Research опубликовала: настройки уровня репозитория в Claude Code могли запустить MCP-серверы до того, как пользователь подтвердил доверие (CVE-2025-59536), а вторая уязвимость отправляла API-ключ на выбранный атакующим адрес (CVE-2026-21852). Anthropic исправила обе до публикации.
- Реестры. В том же исследовании OX 9 из 11 реестров MCP приняли тестовый пакет, который в OX называют «вредоносным пробным шаром».
Неприятнее всего другое, и это следует из отчёта OWASP: у большинства событий безопасности ИИ в первом квартале 2026 года вообще не было CVE, а первой в списке причин стоит неверная конфигурация (например, агенты с избыточными правами). Одна лента CVE пропустит большую часть того, что здесь может вам навредить.
Писать свой MCP-сервер или брать публичный?
По умолчанию мы сами пишем тонкий MCP-сервер на Python или Node поверх API, который уже контролируем и защищаем. Публичные серверы берём, только если версия зафиксирована точно, исходники этой версии прочитаны, а запуск идёт в песочнице. Своя разработка стоит нам поддержки, и протокол действительно движется: Python SDK v2.0.0 вышел 28 июля 2026 года, переименовал FastMCP в MCPServer и перевёл v1 в режим «только исправления безопасности».
Как мы решаем по каждой интеграции:
- Берём удалённый сервер, который вендор сам запускает для собственного API, с OAuth и областями доступа, достаточно узкими, чтобы выдавать токены только на чтение.
- Берём с условиями пакет от сообщества: точная версия, хэш в lock-файле, прочитанный исходник, контейнер, сети нет, если она не нужна.
- Пишем сами, когда инструменты трогают данные клиента или любой путь записи в продакшен, либо когда публичный сервер отдаёт десятки инструментов, а агенту нужны три.
- Отказываемся от всего, что запускается как
npx -y some-serverбез версии. Такая строка ставит то, что реестр отдаёт в день запуска.
От одной привычки пришлось отказаться: считать широко установленный сервер наполовину проверенным. Популярность больше не входит в наше ревью. Пакет postmark-mcp в npm вёл себя как обычный почтовый инструмент, пока версия 1.0.16 не начала молча добавлять во все исходящие письма скрытую копию на внешний адрес (Snyk, 25 сентября 2025 года). Предыдущие версии были чистыми, и поэтому фиксация версии важнее репутации.
Сначала инвентаризация: теневой сервер есть всегда
До любых других мер мы пишем список. Для каждого сервера: владелец, транспорт, SDK и версия, способ аутентификации, какие учётные данные он держит, какие инструменты отдаёт, чтение или запись. Первая находка любого ревью — сервер, которого нет в списке; обычно его добавил разработчик в локальный конфиг IDE. В OWASP это MCP09:2025, Shadow MCP Servers. Каждую запись утверждаем один раз и переутверждаем при любом изменении, включая изменение описаний инструментов.
Запись выглядит так (YAML, лежит рядом с кодом, который вызывает агента):
- name: billing-readonly
owner: payments tech lead
transport: streamable-http
url: https://mcp.internal.example/billing
sdk: mcp (Python) 2.3.0
protocol: [2026-07-28, 2025-11-25]
auth: oauth2.1, audience https://mcp.internal.example/billing
scopes: [invoices:read]
credentials_held: none, per-user tokens only
tools: [get_invoice, list_overdue]
tools_list_sha256: 9f2c41d0...e41a
writes: false
approved: 2026-10-02Строка tools_list_sha256 — хэш нормализованного ответа tools/list. Подмена описания инструмента (tool poisoning, MCP03:2025) работает за счёт изменения того, что описание сообщает модели, поэтому смена хэша возвращает сервер на ревью раньше, чем агент увидит его снова. Спецификация от 28.07.2026 говорит то же: аннотации инструментов недоверенные, если сервер сам не доверенный.
Не все серверы заслуживают одинакового подхода. Читайте правую колонку таблицы ниже как минимум, который мы требуем, прежде чем агент получит сервер; две левые колонки объясняют, почему по строкам это различается.
Риск MCP-сервера по типам
| Тип сервера | Типичный риск | Наша мера по умолчанию |
|---|---|---|
| Локальный STDIO-сервер, запущенный разработчиком | Конфиг становится командой; работает с полными правами разработчика | Контейнер, монтирование только на чтение, учётные данные из окружения только для этого сервера |
| Публичный удалённый сервер (сторонний) | Подмена описаний инструментов, слишком широкие области доступа, уход данных к вендору, которого мы не оценивали | Области только на чтение, зафиксированный хэш описаний, allowlist на каждого агента |
| Удалённый сервер, который мы сами держим для внутренних API | Общий сервисный ключ, передача токена дальше, открытость без аутентификации | OAuth 2.1 resource server, проверка audience, закрытая сеть |
| Сервер с правом записи в продакшен-системы | Одна внедрённая инструкция превращается в реальное изменение | Подтверждение человеком на каждую запись, step-up областей, полный аудит |
| Пакет, установленный из реестра | Вредоносный или перехваченный релиз | Точная версия и хэш, прочитанный исходник, повторное ревью при обновлении |
Как MCP-серверу аутентифицировать клиентов?
Удалённый MCP-сервер по HTTP — это OAuth 2.1 resource server. Спецификация авторизации от 28.07.2026 требует: он ДОЛЖЕН публиковать Protected Resource Metadata (RFC 9728), ДОЛЖЕН проверять, что токен выпущен для него как для audience, и НЕ ДОЛЖЕН принимать или передавать дальше любой другой токен. Клиенты ДОЛЖНЫ отправлять параметр resource из RFC 8707 с именем сервера, а токены НЕ ДОЛЖНЫ передаваться в строке запроса. STDIO-серверы стоят вне этой схемы: по спецификации им НЕ СЛЕДУЕТ использовать поток OAuth, учётные данные они берут из окружения.
Первый вызов без аутентификации должен получать вот что, прямо из спецификации:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
scope="files:read"На практике это четыре правила. Токены выдаются на пользователя, и сервер действует с правами этого человека. Области доступа начинаются с минимума, а запись запрашивает больше через step-up с 403 insufficient_scope, который описан в спецификации. За сервером нет общего сервисного ключа. И сервер отклоняет токен, выпущенный для чего-то другого; это защита от проблемы confused deputy, которую разбирает страница спецификации с рекомендациями по безопасности.
Проверьте настройку audience в своём SDK. TypeScript SDK 2.3.0 (2 октября 2026 года) добавил expectedResource в requireBearerAuth, и в заметках к релизу сказано, что без явной настройки проверка выключена.
STDIO-серверы и конфиги — это код
Локальный STDIO-сервер — процесс с правами того, кто его запустил, поэтому мы запускаем его в контейнере: проект монтируется только на чтение, стоит --network none, если инструменту не нужен конкретный хост, а тогда добавляется allowlist исходящего трафика с этим хостом. Учётные данные передаются переменными окружения, по одной на сервер, и никогда не из личного облачного профиля разработчика.
Файлы, которые запускают такие серверы (.mcp.json, MCP-конфиги редакторов, .claude/settings.json), проходят код-ревью как любой скрипт, с записью в CODEOWNERS. В документации Claude Code теперь прямо сказано, что клонированный репозиторий не может сам одобрить свои серверы. Мы всё равно никогда не включаем enableAllProjectMcpServers и любые аналоги общего одобрения. Находки Check Point относились именно к этому классу проблем: конфигурация репозитория решает, что запустится, раньше, чем человек согласился.
Вывод инструмента — чужой ввод
Любой результат инструмента — недоверенные данные, каким бы внутренним ни был сервер. Текст заявки или загруженная веб-страница могут нести инструкции, а у модели нет надёжного способа отличить их от ваших. DuneSlide показал это наглядно: в Cursor до версии 3.0 безобидный запрос, подтянувший внедрённое содержимое, мог заставить агента указать working_directory за пределами рабочей области и перезаписать вспомогательный файл песочницы (CVE-2026-50548, опубликовано в NVD 25 июня 2026 года, оценка CVSS 4.0 — 9,3, исправлено в 3.0).
Наше правило опирается на «смертельную триаду» (lethal trifecta) Саймона Уиллисона: приватные данные, недоверенное содержимое и способ отправить данные наружу. Один агент не получает все три сразу. Если он читает записи клиентов и при этом читает входящую почту, исходящего HTTP-инструмента и отправки писем у него нет. Записи идут через подтверждение человеком, которое показывает точные аргументы, что получит инструмент.
Общие меры для агентов описаны в статье «Безопасность ИИ-агентов в продакшене», а сторону внедрения инструкций на уровне LLM разбирает материал «OWASP Top 10 для LLM-приложений». Здесь только то, что меняется, когда содержимое приходит через MCP.
28 июля 2026 года: спецификация стала stateless
Ревизия 2026-07-28 убрала сессии на уровне протокола и заголовок Mcp-Session-Id, отказалась от рукопожатия initialize (теперь каждый запрос несёт версию протокола и возможности в _meta) и сделала server/discover обязательным для серверов. Также удалён ping, ужесточена проверка издателя (issuer) в OAuth и описано распространение W3C trace context. Руководство Stacklok по готовности прямо говорит о последствии: клиенты, поддерживающие только новую версию, не работают с серверами, поддерживающими только старую, автоматически.
У политики устаревания есть подвох. Минимальное окно в двенадцать месяцев распространяется на функции с пометкой Deprecated (Roots, Sampling, Logging, старый транспорт HTTP+SSE, Dynamic Client Registration). У удалённых функций окна нет. Сервер, который опирается на сессии 2025-11-25, не получает года отсрочки. Его срок — день, когда его клиенты перейдут только на новую версию.
Порядок для уже работающих серверов:
- Возьмите инвентаризацию выше и добавьте версию протокола, на которой говорит каждый сервер.
- Разнесите серверы по тому, что они используют: удалённые функции, устаревающие функции или ни то ни другое.
- Обновите SDK. Python
mcp2.x обслуживает 2026-07-28 и всех клиентов эпохи 2025 с одного сервера; тем, кто не готов, можно зафиксироватьmcp>=1.28,<2и получать только исправления безопасности. - Тестируйте с выключенной привязкой сессий (session affinity) на балансировщике. Sticky-сессии прячут ошибки состояния, которые новая спецификация делает фатальными.
- Выкатывайте по одному серверу, за флагом, оставив прежнюю сборку готовой к развёртыванию.
Шаги 1 и 2 обычно снова находят теневые серверы.
Что мы логируем при каждом вызове инструмента?
На каждый вызов: пользователь и ID клиента, сервер, инструмент, хэш аргументов (сами аргументы — только если в них нет персональных данных), размер результата, решение (разрешено, отклонено или одобрено и кем) и trace ID. Отсутствие этого OWASP называет MCP08:2025, Lack of Audit and Telemetry. Последнее поле упрощает новая спецификация: traceparent передаётся в _meta, а Python SDK v2 поставляется с трассировкой OpenTelemetry, включённой по умолчанию.
Для агентов, которые трогают деньги или персональные данные, журнал должен пережить атакующего, получившего доступ к хосту, поэтому мы пишем его в хранилище с защитой от подмены. Как это устроено, описано в статье «SealedRun: журнал аудита ИИ-агентов, где подмена видна».
В следующий раз мы включили бы MCP-серверы в область первого теста на безопасность поимённо, а не оставляли на более поздний раунд. Пентест, который знает audience токена и инструменты записи, находит то, мимо чего проходит типовая область для веб-приложения. Если нужен второй взгляд на ваш собственный список, начать можно со страницы услуг по безопасности.