flawopen.com/Teardowns/cve-2026-90970-gitlab-ai-gateway-prompt-template-sandbox-escape
CVE-2026-90970: выход из песочницы шаблонов GitLab AI Gateway
Специально настроенный flow Duo Agent Platform мог выйти из песочницы шаблонов prompt в AI Gateway и выполнять команды. Операторам собственного Gateway следует установить исправленную версию своей ветки GitLab.[1], [4]
Представьте сотрудника, который составляет отчёт по записям разговора. Он должен копировать разрешённые поля как обычный текст. Но специально подготовленный шаблон мог заставить его нажать в папке кнопку «восстановить», и приложение собирало из сохранённых данных активные объекты вместо простого копирования слов. Так нарушалась граница между отображением данных и их интерпретацией приложением. В отчёте исследователя описан конкретный путь через parse_raw() и pickle; бюллетень GitLab подтверждает выход из песочницы и выполнение команд, но не раскрывает все шаги.[3], [6]
Ключевые понятия и термины
AI Gateway- Сервис GitLab, соединяющий функции ИИ с поставщиками моделей; собственный образ Gateway имеет отдельную версию.[5], [1]
Prompt template- Шаблон, объединяющий постоянные инструкции со значениями времени выполнения, например входами flow и контекстом диалога.[3], [6]
Связи со стандартами
- Ссылка CWE для слабого места, описанного в этом руководстве: CWE-1336
Анализ первопричины
GitLab подтверждает, что при определённых условиях аутентифицированный пользователь с доступом к Duo Agent Platform мог использовать специальную конфигурацию flow, чтобы выйти из песочницы шаблонов prompt и выполнять произвольные команды на AI Gateway. Это CWE-1336, CVSS 9,9. Бюллетень не описывает все условия и точную уязвимую функцию. Публичный коммит от 17.09.2026 добавляет PromptSandboxedEnvironment, который, согласно описанию, запрещает доступ к методам связанных объектов, и усиливает операции и фильтры Jinja. Это согласуется с ограничением действий шаблонов над объектами времени выполнения, но коммит не упоминает этот CVE и публичные источники не связывают его с исправленными образами. Считайте его отдельным публичным усилением; публичные источники не связывают его с этим CVE или исправленными образами. В публичной задаче GitLab № 1850 описан более конкретный путь: шаблон Jinja вызывает parse_raw() у объекта HumanMessage из LangChain в истории диалога, что запускает десериализацию pickle. В задаче сказано, что этот сценарий продемонстрировал исследователь в тестовой среде GitLab; бюллетень поставщика не описывает все затронутые условия.[1], [3], [4], [2]
Исправленные версии самостоятельно размещаемого AI Gateway
| Развёртывание / ветка релиза | Исправленная версия AI Gateway | Действие оператора |
|---|---|---|
| Самостоятельно размещённый GitLab, ветка 19.2 | 19.2.4 | Обновите до соответствующего тега самостоятельно размещаемого образа Gateway и проверьте запущенный образ. |
| Самостоятельно размещённый GitLab, ветка 19.3 | 19.3.2 | Обновите до соответствующего тега самостоятельно размещаемого образа Gateway и проверьте запущенный образ. |
| Самостоятельно размещённый GitLab, ветка 19.4 | 19.4.1 | Обновите до соответствующего тега самостоятельно размещаемого образа Gateway и проверьте запущенный образ. |
| AI Gateway под управлением GitLab | GitLab сообщает, что исправление уже установлено. | Убедитесь, что используется Gateway под управлением GitLab; рекомендации для самостоятельно размещаемого образа здесь неприменимы. |
Пошаговый сценарий атаки
Выполнение шаблона пересекает границу песочницы
Публичная задача GitLab № 1850 сообщает, что шаблон мог вызвать parse_raw() у HumanMessage из LangChain в истории диалога и запустить десериализацию pickle. Это путь, описанный исследователем; не считайте его полным для всех уязвимых сценариев или подтверждённым патчем поставщика.[3]
Исходный код: Уязвимый vs Защищённый вариант
В задаче GitLab 1850 сообщается, что уязвимый flow передавал объект HumanMessage из LangChain в Jinja, где шаблон мог вызвать parse_raw() и запустить десериализацию pickle. Этот защитный пример не передаёт объекты сообщений в контекст шаблона, передаёт только текст и запрещает доступ к вызываемым атрибутам и методам. Он показывает границу, которую нужно закрыть, но не является выпущенным патчем GitLab. Публичный коммит 89b72644 — отдельное усиление безопасности: его заголовок касается записей Jinja block-set, а открытые источники не связывают его с CVE-2026-90970 или исправленными образами. Операторам следует установить версии, указанные поставщиком как исправленные.[3], [2], [6]
Фрагмент исходного кода: ai_gateway/prompts/base.py (описанный уязвимый форматтер; иллюстративная защита)
# Фрагмент уязвимого пути, описанного в задаче GitLab № 1850.
from jinja2 import PackageLoader
from jinja2.sandbox import SandboxedEnvironment
from langchain_core.prompts.string import DEFAULT_FORMATTER_MAPPING
from typing import Any
jinja_loader = PackageLoader("ai_gateway.prompts", "definitions")
jinja_env = SandboxedEnvironment(loader=jinja_loader)
def jinja2_formatter(template: str, /, **kwargs: Any) -> str:
return jinja_env.from_string(template).render(**kwargs)
DEFAULT_FORMATTER_MAPPING["jinja2"] = jinja2_formatter
# Иллюстративный шаблон защиты, а не патч GitLab для этой CVE.
from typing import Any
from jinja2 import StrictUndefined
from jinja2.sandbox import ImmutableSandboxedEnvironment
class DataOnlyPromptEnvironment(ImmutableSandboxedEnvironment):
def is_safe_attribute(self, obj: Any, attr: str, value: Any) -> bool:
if callable(value):
return False
return super().is_safe_attribute(obj, attr, value)
def is_safe_callable(self, obj: Any) -> bool:
return False
def render_prompt(template: str, history: list[Any]) -> str:
safe_history = []
for message in history:
content = message.content
if not isinstance(content, str):
raise TypeError("prompt history must contain text only")
safe_history.append({"content": content})
env = DataOnlyPromptEnvironment(undefined=StrictUndefined)
return env.from_string(template).render(history=safe_history)
Чек-лист по защите системы для инженеров
- ✓Обновите собственный AI Gateway до 19.2.4, 19.3.2 или 19.4.1 соответствующей ветки GitLab и используйте тег образа из руководства.[1], [5]
- ✓Проверьте, собственный ли Gateway или размещённый GitLab. По данным GitLab, GitLab.com, GitLab Dedicated и инстансы с Gateway GitLab уже исправлены.[1]
- ✓Не считайте песочницу шаблонизатора полной изоляцией, если шаблоны получают живые объекты приложения.[6]
- ✓Передавайте только инертные данные; исключите методы, десериализаторы, файловые/процессные дескрипторы и сервисные объекты из контекста.[6], [2]
- ✓Ограничьте атрибуты и вызовы, проверьте фильтры на прямой вызов методов и протестируйте производственную версию и конфигурацию Jinja.[3], [2], [6]
- ✓Добавьте тесты, отклоняющие небезопасные операции с объектами и сохраняющие обычный рендеринг строк; не используйте реальные командные payload.[3], [6]
- ✓Не выводите из одного бюллетеня наличие публичной кампании эксплуатации или полной цепочки. NVD указывает оценку SSVC CISA Coordinator от 02.10.2026: “exploitation: none”; это состояние на указанную дату, а не утверждение о последующих событиях.[1], [4]
Источники
- GitLab AI Gateway Critical Patch Release: 19.2.4, 19.3.2, and 19.4.1
- Коммит GitLab 89b72644 — отдельное усиление без подтверждённой связи: “fix(prompts): block Jinja block-set NSRef writes on non-namespace objects”
- GitLab AI Gateway work item 1850 (reporter account; reproduction material omitted)
- NVD: CVE-2026-90970
- GitLab Self-Hosted AI Gateway installation guide
- Jinja 3.1 Sandbox documentation