flawopen.com/Teardowns/cve-2026-90970-gitlab-ai-gateway-prompt-template-sandbox-escape
CVE-2026-90970: escape del sandbox de prompts de GitLab AI Gateway
Un flujo de Duo Agent Platform especialmente preparado podía escapar del sandbox de plantillas de prompt de AI Gateway y ejecutar comandos. Quienes administren un Gateway propio deben actualizar a la versión corregida de su rama de GitLab.[1], [4]
Imagina a una persona que rellena un informe a partir de registros de conversación. Debe copiar los campos aprobados como texto. Una plantilla preparada podía indicarle que usara la opción «restaurar» del archivo, haciendo que la aplicación reconstruyera objetos activos a partir de los datos almacenados, en vez de limitarse a copiar palabras. Así se cruza la frontera entre mostrar datos y pedir a la aplicación que los interprete. El informe del investigador documenta la ruta concreta con parse_raw() y pickle; el aviso de GitLab confirma la salida del sandbox y la ejecución de comandos, pero no describe todos los pasos.[3], [6]
Conceptos Clave y Términos
AI Gateway- Servicio de GitLab que conecta funciones de IA con proveedores de modelos; la instalación propia usa una imagen Gateway con versión independiente.[5], [1]
Prompt template- Plantilla que combina instrucciones fijas con valores de ejecución, como entradas del flujo y contexto de conversación.[3], [6]
Mapeos de marcos
- Referencia CWE para la debilidad descrita en esta guía: CWE-1336
Análisis de Causa Raíz
GitLab confirma que, bajo ciertas condiciones, una persona autenticada con acceso a Duo Agent Platform podía usar una configuración de flujo especial para escapar del sandbox de plantillas de prompt y ejecutar comandos arbitrarios en AI Gateway. Es CWE-1336, CVSS 9.9. El aviso no detalla todas las condiciones ni la función vulnerable. Un commit público del 17/09/2026 añade PromptSandboxedEnvironment, descrito como una clase que impide acceder a métodos de objetos enlazados, y refuerza operaciones y filtros de Jinja. Esto concuerda con limitar las acciones de las plantillas sobre objetos de ejecución, pero el commit no cita el CVE y las fuentes revisadas no lo vinculan con las imágenes corregidas. Considéralo un cambio público de refuerzo independiente; ninguna fuente pública lo vincula con este CVE ni con las imágenes corregidas. El elemento público nº 1850 de GitLab describe una ruta más concreta: una plantilla Jinja llama a parse_raw() sobre un HumanMessage de LangChain del historial de conversación y activa la deserialización pickle. El elemento atribuye esta demostración al informe del investigador y dice que ocurrió en el entorno de staging de GitLab; el aviso del proveedor no detalla todas las condiciones afectadas.[1], [3], [4], [2]
Matriz de versiones corregidas del AI Gateway autogestionado
| Despliegue / rama de versión | Versión corregida de AI Gateway | Acción del operador |
|---|---|---|
| GitLab autogestionado, rama 19.2 | 19.2.4 | Actualiza a la etiqueta correspondiente de la imagen autogestionada de Gateway y verifica la imagen desplegada. |
| GitLab autogestionado, rama 19.3 | 19.3.2 | Actualiza a la etiqueta correspondiente de la imagen autogestionada de Gateway y verifica la imagen desplegada. |
| GitLab autogestionado, rama 19.4 | 19.4.1 | Actualiza a la etiqueta correspondiente de la imagen autogestionada de Gateway y verifica la imagen desplegada. |
| AI Gateway alojado por GitLab | GitLab afirma que ya estaba corregido. | Confirma que el despliegue usa el Gateway alojado por GitLab; no apliques aquí las instrucciones para imágenes autogestionadas. |
Flujo de Ataque Paso a Paso
La plantilla cruza el límite del sandbox
El elemento público nº 1850 de GitLab informa que una plantilla podía llamar a parse_raw() sobre un HumanMessage de LangChain del historial de conversación y activar la deserialización pickle. Esta es la ruta documentada por el investigador; no supongas que cubre todos los flujos afectados ni que representa el parche del proveedor.[3]
Código Fuente: Vulnerable vs. Seguro
El elemento 1850 de GitLab informa que el flujo vulnerable exponía a Jinja un objeto HumanMessage de LangChain, sobre el que una plantilla podía invocar parse_raw() y activar la deserialización pickle. Este ejemplo defensivo mantiene los objetos de mensaje fuera del contexto de la plantilla, pasa solo texto y deniega el acceso a atributos invocables y métodos. Ilustra el límite que debe cerrarse; no es el parche publicado por GitLab. El commit público 89b72644 es un cambio de seguridad independiente: su título trata de escrituras Jinja block-set y ninguna fuente pública lo vincula con CVE-2026-90970 ni con las imágenes corregidas. Los operadores deben seguir las versiones corregidas indicadas por el proveedor.[3], [2], [6]
Fragmento de origen: ai_gateway/prompts/base.py (formateador vulnerable descrito; diseño defensivo ilustrativo)
# Fragmento de la ruta vulnerable descrita en el elemento de trabajo 1850 de GitLab.
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
# Patrón de defensa ilustrativo; no es el parche de GitLab para este 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)
Lista de Verificación de Seguridad para Ingeniería
- ✓Actualiza AI Gateway autohospedado a 19.2.4, 19.3.2 o 19.4.1 de la rama compatible y usa la etiqueta de imagen de la guía de GitLab.[1], [5]
- ✓Determina si el Gateway es propio o alojado por GitLab. GitLab indica que GitLab.com, GitLab Dedicated e instancias con Gateway alojado ya estaban corregidos.[1]
- ✓No consideres el sandbox de plantillas como aislamiento completo si recibe objetos vivos de la aplicación.[6]
- ✓Pasa solo datos inertes a las plantillas; excluye métodos, deserializadores, manejadores de archivos/procesos y objetos de servicio.[6], [2]
- ✓Restringe atributos y llamadas, audita filtros que invocan métodos directamente y prueba la versión y configuración Jinja de producción.[3], [2], [6]
- ✓Añade pruebas que rechacen operaciones inseguras sobre objetos y mantengan el renderizado normal de cadenas; no uses cargas reales de comandos.[3], [6]
- ✓No deduzcas una campaña pública de explotación ni una cadena completa solo a partir del aviso. NVD registra la evaluación SSVC del CISA Coordinator como “exploitation: none” el 02/10/2026; es un estado fechado, no una afirmación sobre actividad posterior.[1], [4]
Fuentes
- GitLab AI Gateway Critical Patch Release: 19.2.4, 19.3.2, and 19.4.1
- Commit de GitLab 89b72644 — refuerzo independiente y sin vínculo confirmado: “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