flawopen.com/Teardowns/cve-2026-90970-gitlab-ai-gateway-prompt-template-sandbox-escape
CVE-2026-90970 : évasion du sandbox de prompts de GitLab AI Gateway
Un flux Duo Agent Platform spécialement conçu pouvait sortir du sandbox des modèles de prompt d’AI Gateway et exécuter des commandes. Les opérateurs d’une passerelle auto-hébergée doivent installer la version corrigée de leur branche GitLab.[1], [4]
Imaginez une personne qui remplit un rapport à partir de l’historique d’une conversation. Elle doit copier les champs autorisés comme du texte. Un modèle spécialement conçu pouvait lui faire utiliser la commande « restaurer » du dossier, ce qui amenait l’application à reconstruire des objets actifs depuis les données stockées au lieu de simplement copier des mots. La frontière entre afficher des données et demander à l’application de les interpréter était alors franchie. Le rapport du chercheur décrit le chemin précis avec parse_raw() et pickle ; l’avis de GitLab confirme la sortie du sandbox et l’exécution de commandes, sans détailler toutes les étapes.[3], [6]
Concepts Clés et Termes
AI Gateway- Service GitLab reliant les fonctions d’IA aux fournisseurs de modèles ; les installations auto-hébergées ont une image Gateway versionnée séparément.[5], [1]
Prompt template- Modèle combinant des instructions fixes avec des valeurs d’exécution, comme les entrées de flux et le contexte de conversation.[3], [6]
Correspondances de référentiels
- Référence CWE pour la faiblesse décrite dans ce guide : CWE-1336
Analyse de Cause Racine
GitLab confirme que, dans certaines conditions, un utilisateur authentifié ayant accès à Duo Agent Platform pouvait utiliser une configuration spéciale pour sortir du sandbox des modèles de prompt et exécuter des commandes arbitraires sur AI Gateway. Le problème est classé CWE-1336, CVSS 9,9. L’avis ne précise pas toutes les conditions ni la fonction vulnérable. Un commit public du 17/09/2026 ajoute PromptSandboxedEnvironment, décrit comme empêchant l’accès aux méthodes d’objets liés, et renforce les opérations et filtres Jinja. Cela concorde avec la restriction des actions des modèles sur les objets d’exécution, mais le commit ne cite pas ce CVE et les sources examinées ne le relient pas aux images corrigées. Présentez-le comme un renforcement public distinct ; aucune source publique ne le relie à ce CVE ni aux images corrigées. L’élément public GitLab nº 1850 décrit un chemin plus précis : un modèle Jinja appelle parse_raw() sur un HumanMessage de LangChain présent dans l’historique de conversation, ce qui déclenche la désérialisation pickle. L’élément attribue cette démonstration au rapport du chercheur et indique qu’elle s’est produite dans l’environnement de staging de GitLab ; l’avis du fournisseur ne détaille pas toutes les conditions concernées.[1], [3], [4], [2]
Versions corrigées de l’AI Gateway auto-hébergé
| Déploiement / branche | Version corrigée de l’AI Gateway | Action de l’opérateur |
|---|---|---|
| GitLab auto-hébergé, branche 19.2 | 19.2.4 | Passez au tag correspondant de l’image Gateway auto-hébergée et vérifiez l’image déployée. |
| GitLab auto-hébergé, branche 19.3 | 19.3.2 | Passez au tag correspondant de l’image Gateway auto-hébergée et vérifiez l’image déployée. |
| GitLab auto-hébergé, branche 19.4 | 19.4.1 | Passez au tag correspondant de l’image Gateway auto-hébergée et vérifiez l’image déployée. |
| AI Gateway hébergé par GitLab | GitLab indique qu’il était déjà corrigé. | Vérifiez que le déploiement utilise le Gateway hébergé par GitLab ; les consignes d’image auto-hébergée ne s’appliquent pas ici. |
Déroulement de l'Attaque Étape par Étape
Le modèle franchit la limite du sandbox
L’élément public GitLab nº 1850 indique qu’un modèle pouvait appeler parse_raw() sur un HumanMessage de LangChain de l’historique de conversation et déclencher la désérialisation pickle. C’est le chemin documenté par le chercheur ; ne supposez pas qu’il couvre tous les modèles concernés ni qu’il correspond au correctif du fournisseur.[3]
Code Source : Vulnérable vs Sécurisé
L’élément GitLab 1850 indique que le flux vulnérable exposait un objet HumanMessage de LangChain à Jinja, où un modèle pouvait appeler parse_raw() et déclencher la désérialisation pickle. Cet exemple défensif garde les objets de message hors du contexte du modèle, ne transmet que du texte et refuse l’accès aux attributs appelables et aux méthodes. Il illustre la frontière à fermer ; ce n’est pas le correctif publié par GitLab. Le commit public 89b72644 constitue un changement de sécurité distinct : son titre porte sur les écritures Jinja block-set, et aucune source publique ne le relie à CVE-2026-90970 ni aux images corrigées. Les opérateurs doivent suivre les versions corrigées indiquées par le fournisseur.[3], [2], [6]
Extrait source: ai_gateway/prompts/base.py (formateur vulnérable signalé ; défense illustrative)
# Extrait du chemin vulnérable signalé dans l’élément de travail 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
# Modèle de défense illustratif ; ce n’est pas le correctif de GitLab pour cette 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)
Liste de Contrôle de Sécurité pour l'Ingénierie
- ✓Mettez à niveau AI Gateway auto-hébergé vers 19.2.4, 19.3.2 ou 19.4.1 de la branche compatible et utilisez l’image indiquée par la documentation GitLab.[1], [5]
- ✓Déterminez si la passerelle est auto-hébergée ou hébergée par GitLab. GitLab indique que GitLab.com, GitLab Dedicated et les instances utilisant une passerelle GitLab étaient déjà corrigés.[1]
- ✓Ne considérez pas le sandbox de modèles comme une isolation complète lorsque les modèles reçoivent des objets applicatifs vivants.[6]
- ✓Fournissez uniquement des données inertes ; excluez méthodes, désérialiseurs, handles de fichiers/processus et objets de service du contexte.[6], [2]
- ✓Restreignez attributs et appels, auditez les filtres qui appellent directement des méthodes et testez la version/configuration Jinja de production.[3], [2], [6]
- ✓Ajoutez des tests qui rejettent les opérations dangereuses sur objets et préservent le rendu de chaînes ; n’utilisez pas de vraies charges de commande.[3], [6]
- ✓Ne déduisez ni campagne publique d’exploitation ni chaîne complète du seul avis. Le NVD indique que l’évaluation SSVC du CISA Coordinator était « exploitation: none » au 02/10/2026 ; il s’agit d’un état daté, pas d’une affirmation sur une activité ultérieure.[1], [4]
Sources
- GitLab AI Gateway Critical Patch Release: 19.2.4, 19.3.2, and 19.4.1
- Commit GitLab 89b72644 — renforcement distinct sans lien confirmé : “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