От переводчика: agentgateway видится весьма полезным элементом архитектуры agentic enterprise. По сути, это API Gateway, адаптированный под агентный/MCP-мир. Так что, придется осваивать, себе я галочку поставил. А пока держите практическую статью, как с ним работать.

Корпоративные команды используют MCP-шлюзы, чтобы контролировать, как ИИ-агенты получают доступ к инструментам, сервисам и корпоративным данным. Agentgateway, проект с открытым исходным кодом, развиваемый под эгидой Agentic AI Foundation, маршрутизирует MCP-, Agent-to-Agent-, LLM- и API-трафик между клиентами и внутренними сервисами. Его открытый код, тесты и рекомендации по безопасности делают его полезным объектом для практического изучения безопасности корпоративных шлюзов.

Мои предыдущие исследования безопасности были посвящены тому, как ИИ-агенты взаимодействуют с окружающей их инфраструктурой. В этой статье я хотел подробнее изучить agentgateway и задать более узкий вопрос: что происходит, когда представление шлюза об операции отличается от того, что получает вышестоящий сервис? Трансляция протоколов, инспекция тела запроса и повторное использование сессий — каждый из этих механизмов создает свою версию этой проблемы.

Изучая эти пути, я счел полезным рассматривать три практических контракта. Аргументы инструментов должны оставаться в предназначенных для них HTTP-полях. Политика должна позволять определить, увидела ли она полный запрос. Шлюз должен сохранять выбор backend-сервиса для состояния сессии между запросами. То, как agentgateway сейчас обрабатывает эти случаи, показывает, как эти контракты применяются к stateful MCP и stateless-протоколу 2026-07-28.

Рисунок 1. Три контракта MCP-шлюза обеспечивают сохранение границ HTTP-полей, полноту инспекции и привязку к backend-сервису.
Рисунок 1. Три контракта MCP-шлюза обеспечивают сохранение границ HTTP-полей, полноту инспекции и привязку к backend-сервису.

Контракт трансляции сохраняет границы HTTP-полей

Трансляция протоколов позволяет MCP-клиенту вызывать существующий сервис OpenAPI. Она преобразует управляемый клиентом JSON в HTTP-пути, строки запросов и заголовки.

Ранее опубликованное исследование показывает, как при такой трансляции могут возникать проблемы. Исследование @spacewander проверяло Higress, agentgateway, LiteLLM и Unla. Проекты использовали разные языки и HTTP-стеки, но каждый из них позволял аргументам инструментов изменять части исходящего запроса сверх предусмотренных для них значений параметров. Затронутыми полями были пути, строки запросов и заголовки.

В agentgateway v0.12.0 изменился способ формирования исходящих запросов. Исправление добавляет percent-encoding для подставляемых значений параметров пути, ключей и значений параметров запроса. Шлюз принимает только заголовки, объявленные в схеме OpenAPI, и не позволяет аргументам инструментов заменять защищенные или уже существующие заголовки. Agentgateway также опубликовал рекомендации по безопасности для CVE-2026-29791. После того как адаптер сформировал другой запрос, HTTP-клиент уже не может восстановить исходные границы полей. Шлюз должен сохранять каждый аргумент в том поле, которое указано в схеме.

Контракт инспекции явно учитывает полноту

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

В ходе проверки безопасности политик Common Expression Language (CEL), выполняемых во время обработки запросов в agentgateway, я обнаружил расхождение между телом, которое проверяла политика, и телом, отправляемым upstream. До версии v1.4.0 слишком большое тело появлялось в request.body как первые maxBufferSize байт без какого-либо сигнала о полноте. При этом agentgateway всё равно передавал upstream полное тело запроса.

Правило запрета использовало string(request.body).contains("attacker-payload"). Небольшой запрос, содержащий совпадение, блокировался, но если поместить то же значение после большого поля с JSON-padding, оно оказывалось за границей инспекции. Политика видела безобидный префикс, в то время как upstream разбирал полный корректный JSON-документ.

Для выражений, выполняемых во время обработки запросов, agentgateway v1.4.0 явно указывает полноту тела. request.body доступен только тогда, когда полное тело помещается в maxBufferSize. Если это не так, вычисление выражения завершается ошибкой. request.bodyPrefix предоставляет полное тело, если оно помещается в лимит, или первые maxBufferSize байт, если не помещается.

Такая ошибка вычисления не означает, что политика Deny, зависящая от тела запроса, работает по принципу fail closed. Если выражение Deny не удалось вычислить, запрос не отклоняется, поэтому документация рекомендует использовать Allow или Require, когда ошибка вычисления должна приводить к блокировке.

request.bodyPrefix никогда не означает, что политика увидела полное тело. Он подходит, когда правило зависит непосредственно от префикса, например от фиксированной сигнатуры файла или преамбулы протокола. Проверка наличия JSON-поля, вложенного объекта или запрещённого значения в любой части тела требует полного запроса.

Контракт сессии сохраняет привязку к backend

Stateful MCP позволяет клиенту продолжать серверную сессию между запросами. В деплое 2025-11-25 после инициализации клиент отправляет Mcp-Session-Id. Перед применением политики авторизации текущего маршрута шлюзу необходимо убедиться, что сессия принадлежит выбранному backend.

До версии v1.4.0 agentgateway мог повторно использовать MCP-сессию, не проверяя, выбрал ли текущий запрос тот же backend. Текущий запрос предоставлял политику авторизации MCP, а сессия — существующее исходящее соединение.

Я воспроизвёл это несоответствие с двумя маршрутами. /sensitive выбирал sensitive.example.com с политикой deny-all-traffic. /permissive выбирал permissive.example.com с политикой allow-all-traffic.

В версиях agentgateway до v1.4.0 авторизация MCP не препятствовала инициализации сессии через /sensitive. После этого клиент мог повторно использовать тот же идентификатор сессии через /permissive. Agentgateway применял allow-all-traffic от разрешающего маршрута, отправляя при этом вызов инструмента по существующему соединению с sensitive.example.com.

Запрос использовал политику одного маршрута и соединение другого:

MCP authorization policy = allow-all-traffic (/permissive) upstream connection = sensitive.example.com (/sensitive)

Я поделился воспроизведением проблемы с командой agentgateway. Джон Ховард подтвердил её, и мы выяснили, что проблема затрагивала конфигурации stateful MCP с несколькими backend-сервисами на разных маршрутах. Agentgateway опубликовал GHSA-mvgg-jvj2-4frq с рейтингом High и оценкой CVSS 8.1.

Agentgateway v1.4.0 теперь сохраняет ResourceName выбранной группы backend-сервисов как backend_id в каждом SessionEntry. Перед возобновлением сессии он сравнивает это значение с группой backend-сервисов, выбранной для текущего запроса, и отклоняет запрос при несовпадении. Патч включает регрессионный тест для сценария с разными backend-сервисами.

sequenceDiagram
    autonumber
    participant C as MCP client
    participant R as Agentgateway router
    participant M as Session manager
    participant S as sensitive.example.com
C->>R: initialize on /sensitive
    Note over R: Select sensitive backend group
    R->>S: initialize
    S-->>R: initialize succeeds
    Note over R: Prepare Mcp-Session-Id: SID-A
    R->>M: insert_session(sensitive, session, ttl)
    Note over M: Store backend_id ResourceName(sensitive)
    R-->>C: SID-A

В agentgateway идентификатор сессии может восстановить исходящее соединение. Поэтому agentgateway проверяет сохраненный backend перед тем, как использовать настройки авторизации из текущего запроса.

Stateless MCP меняет контракт сессии

MCP 2026-07-28 удаляет возможность повторного использования протокольной сессии. Протокол больше не использует рукопожатие initialize или Mcp-Session-Id. Версия протокола, идентификатор клиента и возможности клиента теперь передаются с каждым запросом, поэтому шлюзам больше не нужны sticky routing или общее хранилище для протокольных сессий MCP.

Это изменение устраняет только необходимость привязывать протокольную сессию MCP к backend. Stateful MCP 2025-11-25 по-прежнему требует описанной выше проверки backend, когда шлюз создаёт сессию. В MCP 2026-07-28 нет протокольной сессии, которую нужно было бы привязывать. Ни одна из версий не устраняет риски трансляции или инспекции тела там, где используются соответствующие функции. Аутентификация, ограничения частоты запросов и функции приложения по-прежнему могут хранить собственное состояние за пределами протокола MCP.

Agentgateway v1.4.0 поддерживает обе версии. Он проверяет backend для stateful-клиентов и обрабатывает запросы MCP 2026-07-28 без протокольных сессий. Версия протокола в каждом запросе определяет, по какому пути он будет обработан.

Безопасность корпоративного MCP-шлюза выходит за рамки этих случаев

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

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

Подпишитесь на канал Agentic Enterpise — о жизни ИИ‑агентов в кровавом энтерпрайзе

Комментарии (0)