Las chat templates son código: inyección Jinja2 (SSTI) en servidores de inferencia LLM
El boletín VU#915947 del CERT/CC (20 de abril de 2026) documenta CVE-2026-5760, una RCE CVSS 9.8 en SGLang: un archivo de modelo GGUF malicioso transporta una chat template Jinja2 que ejecuta Python en el servidor. La misma clase que Llama Drama y un fallo de vLLM anterior.
¿Qué es esto?
El 20 de abril de 2026, el CERT Coordination Center publicó VU#915947, que describe CVE-2026-5760 — un fallo de ejecución remota de código (RCE) con puntuación CVSS 9.8 en SGLang, un framework de servicio de inferencia de código abierto para grandes modelos de lenguaje ampliamente desplegado (el proyecto supera las 26 000 estrellas y los 5 500 forks). El detonante no es un paquete de red ni una petición malformada: es un archivo de modelo. Un modelo GGUF manipulado transporta una chat template que, una vez cargado el modelo y en cuanto una petición alcanza el endpoint de reranking, ejecuta Python proporcionado por el atacante en el servidor de inferencia.
La relevancia trasciende un único proyecto, porque CVE-2026-5760 no es nueva en su naturaleza. Pertenece a la misma clase que CVE-2024-34359 — «Llama Drama», una RCE CVSS 9.7 en llama-cpp-python divulgada en 2024 y ya corregida — y que el aviso de vLLM CVE-2025-61620 que endureció la misma superficie de ataque a finales de 2025. Tres frameworks distintos, un mismo error recurrente: tratar una chat template suministrada por el modelo como dato inerte cuando, en realidad, es código ejecutable.
Cómo funciona
Los tokenizadores modernos incluyen un chat_template: una plantilla Jinja2 que formatea los mensajes en bruto hacia la secuencia exacta de tokens que el modelo espera. Esa plantilla viaja dentro del artefacto del modelo — en un archivo GGUF se almacena en el campo de metadatos tokenizer.chat_template. Cuando un servidor carga el modelo, lee esa cadena y la renderiza, junto con los datos del usuario, en el momento de la petición.
El defecto está en renderizarla con un motor Jinja2 sin restricciones. Según el CERT/CC y el investigador que reportó el fallo (Stuart Beck), SGLang utilizaba jinja2.Environment() en lugar del ImmutableSandboxedEnvironment de Jinja2 para renderizar la plantilla suministrada por el modelo, en entrypoints/openai/serving_rerank.py. Un entorno Jinja2 sin sandbox expone los internos de los objetos Python, lo que constituye la condición típica de una inyección de plantilla del lado del servidor (SSTI) — y una SSTI en un proceso servidor equivale a ejecución de código arbitrario.
La secuencia de extremo a extremo se describe sin entregar ningún payload:
# Patrón vulnerable: plantilla suministrada por el modelo, renderizada con todo el poder de Jinja2
from jinja2 import Environment
template = Environment().from_string(tokenizer.chat_template) # ¡entrada no confiable!
rendered = template.render(messages=...) # [REDACTED] se ejecuta aquí
Un atacante publica un modelo GGUF cuyo chat_template contiene una expresión SSTI más una frase de activación (en el caso divulgado, una frase del reranker Qwen3) que dirige la ejecución hacia la ruta de rerank vulnerable. Una víctima descarga y sirve ese modelo — normalmente desde un hub público como Hugging Face — y la primera petición a /v1/rerank renderiza la plantilla y ejecuta el código incrustado. No se requieren credenciales.
Por qué importa
Los servidores de inferencia están cada vez más expuestos a la red y suelen ejecutarse con privilegios amplios (acceso a GPU, metadatos de la nube, acceso a la red interna). Un único modelo envenenado en un hub popular convierte, por tanto, «descargué un modelo» en «un atacante tiene una shell en mi máquina GPU». Como las instrucciones maliciosas viven en el artefacto, cada usuario posterior que sirva ese modelo hereda el compromiso: es un fallo de cadena de suministro, no un error aislado.
El carácter recurrente es la verdadera lección. La misma causa raíz ha aparecido sucesivamente en llama-cpp-python, vLLM y SGLang porque el manejo de chat templates se copia y reimplementa por todo el ecosistema. Allí donde un framework renderiza una plantilla portada por el modelo sin sandbox, el fallo está latente. Según el CERT/CC, no se obtuvo ningún parche del proveedor durante el proceso de coordinación para CVE-2026-5760: en el momento de la divulgación, los operadores debían mitigarlo por su cuenta.
Defensas
- Aísle en sandbox toda plantilla suministrada por un modelo. Renderice las chat templates con el
ImmutableSandboxedEnvironmentde Jinja2, nunca con unEnvironment()desnudo. Es la recomendación principal del CERT/CC y el arreglo que cerró los casos anteriores.
# Mitigación: renderizar plantillas no confiables en un entorno con sandbox
from jinja2.sandbox import ImmutableSandboxedEnvironment
template = ImmutableSandboxedEnvironment().from_string(tokenizer.chat_template)
- Trate los archivos de modelo como código no confiable, no como datos. Cargue solo modelos de fuentes en las que confíe, fije una revisión/hash de commit concreto y verifique sumas de comprobación o firmas. Un hub de modelos es una cadena de suministro de software.
- Inspeccione
tokenizer.chat_templateantes de ponerlo en servicio. Marque, durante la admisión del modelo, toda plantilla que referencie accesos a atributos, builtins o cualquier cosa más allá del simple formateo de mensajes. - Contenga el radio de impacto. Ejecute los servidores de inferencia como cargas de bajo privilegio y aisladas de red; bloquee la salida (egress); corte el acceso a los metadatos de la nube. Si se dispara una SSTI, es el mínimo privilegio el que decide entre la molestia y la brecha.
- Parchee y siga la clase, no solo la CVE. Mantenga al día
llama-cpp-python, vLLM, SGLang y servidores de inferencia similares, y audite cualquier renderizado de plantillas propio en busca del mismo patrónEnvironment().
Estado
| Elemento | Valor |
|---|---|
| Identificador | CVE-2026-5760 / CERT VU#915947 |
| CVSS | 9.8 (crítica) |
| Afectado | SGLang (/v1/rerank, renderizado de chat template GGUF) |
| Causa raíz | jinja2.Environment() en lugar de ImmutableSandboxedEnvironment |
| Divulgación | 20 de abril de 2026 (CERT/CC) |
| Parche | Ninguno obtenido durante la coordinación (mitigar con sandboxing) |
| Misma clase | CVE-2024-34359 «Llama Drama» (corregida); vLLM CVE-2025-61620 (corregida) |
Fechas clave: 2024 — Llama Drama (CVE-2024-34359). Finales de 2025 — arreglo de vLLM (CVE-2025-61620). 20 de abril de 2026 — divulgación de SGLang (CVE-2026-5760).
Este artículo cubre una clase de vulnerabilidad divulgada públicamente con fines defensivos y omite deliberadamente cualquier código de explotación funcional.