FlawOpen

flawopen.com/Teardowns/cve-2026-90970-gitlab-ai-gateway-prompt-template-sandbox-escape

● CVE-2026-90970 · CVSS 9.9[4] · Критический
Исследования · FlawOpen

CVE-2026-90970: выход из песочницы шаблонов GitLab AI Gateway

Специально настроенный flow Duo Agent Platform мог выйти из песочницы шаблонов prompt в AI Gateway и выполнять команды. Операторам собственного Gateway следует установить исправленную версию своей ветки GitLab.[1], [4]

Вектор CVSSCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H[4]
💡 Простыми словами (ELI5)

Представьте сотрудника, который составляет отчёт по записям разговора. Он должен копировать разрешённые поля как обычный текст. Но специально подготовленный шаблон мог заставить его нажать в папке кнопку «восстановить», и приложение собирало из сохранённых данных активные объекты вместо простого копирования слов. Так нарушалась граница между отображением данных и их интерпретацией приложением. В отчёте исследователя описан конкретный путь через parse_raw() и pickle; бюллетень GitLab подтверждает выход из песочницы и выполнение команд, но не раскрывает все шаги.[3], [6]

Ключевые понятия и термины

AI Gateway
Сервис GitLab, соединяющий функции ИИ с поставщиками моделей; собственный образ Gateway имеет отдельную версию.[5], [1]
Prompt template
Шаблон, объединяющий постоянные инструкции со значениями времени выполнения, например входами flow и контекстом диалога.[3], [6]
Sandbox escape
Использование поведения ограниченной среды для пересечения установленной ею границы.[3], [6]
CWE-1336
Неправильная нейтрализация специальных элементов шаблонизатора; запись CVE относит проблему к этой слабости.[4], [1]

Связи со стандартами

  • Ссылка 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.219.2.4Обновите до соответствующего тега самостоятельно размещаемого образа Gateway и проверьте запущенный образ.
Самостоятельно размещённый GitLab, ветка 19.319.3.2Обновите до соответствующего тега самостоятельно размещаемого образа Gateway и проверьте запущенный образ.
Самостоятельно размещённый GitLab, ветка 19.419.4.1Обновите до соответствующего тега самостоятельно размещаемого образа Gateway и проверьте запущенный образ.
AI Gateway под управлением GitLabGitLab сообщает, что исправление уже установлено.Убедитесь, что используется Gateway под управлением GitLab; рекомендации для самостоятельно размещаемого образа здесь неприменимы.

Пошаговый сценарий атаки

Шаг 1

Пользователь может настроить собственный flow

Предпосылка — аутентифицированный пользователь имеет доступ к Duo Agent Platform. Конкретная роль и все необходимые возможности flow не названы.[1], [3]

Шаг 2

Flow передаёт специальную конфигурацию шаблона

Специальная конфигурация flow достигает песочницы шаблонов prompt. Публичный бюллетень не содержит полной безопасной репродукции или точного выражения.[1], [3]

Шаг 3

Выполнение шаблона пересекает границу песочницы

Публичная задача GitLab № 1850 сообщает, что шаблон мог вызвать parse_raw() у HumanMessage из LangChain в истории диалога и запустить десериализацию pickle. Это путь, описанный исследователем; не считайте его полным для всех уязвимых сценариев или подтверждённым патчем поставщика.[3]

Шаг 4

На Gateway могут выполняться команды

Подтверждённое последствие — произвольные команды на AI Gateway, то есть выполнение на сервере, а не только ошибка модели при следовании инструкциям.[1], [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)

Чек-лист по защите системы для инженеров

Источники