flawopen.com/Teardowns/cve-2026-90970-gitlab-ai-gateway-prompt-template-sandbox-escape
CVE-2026-90970: GitLab AI Gateway prompt sandbox escape
A specially crafted Duo Agent Platform flow could escape the AI Gateway prompt-template sandbox and execute commands. Self-hosted Gateway operators should upgrade to the fixed release for their GitLab line.[1], [4]
A prompt template is like a clerk filling a report from a folder of conversation records. The clerk should copy approved fields as plain text. Here, a crafted template could tell the clerk to use the folder’s “restore” control, making the application rebuild live objects from stored data instead of simply copying words. That crosses the boundary between displaying data and asking the application to interpret it. The reporter documents the specific parse_raw()/pickle path; GitLab’s advisory confirms the sandbox escape and command-execution impact without describing every step.[3], [6]
Core Concepts & Subsystem Terms
AI Gateway- GitLab service that connects GitLab AI features to model providers; self-hosted installations have a separately versioned Gateway image.[5], [1]
Prompt template- A template that combines fixed instructions with runtime values, such as flow inputs and conversation context.[3], [6]
Framework mappings
- CWE reference for the weakness described in this guide: CWE-1336
Root Cause Analysis
GitLab's advisory confirms that, under certain conditions, an authenticated user with Duo Agent Platform access could use a specially crafted flow configuration to escape the prompt-template sandbox and execute arbitrary commands on the AI Gateway. It classifies the issue as CWE-1336 and rates it CVSS 9.9. GitLab's public work item #1850 gives a more specific reported path: a Jinja template invokes parse_raw() on a LangChain HumanMessage from conversation history, which permits pickle deserialization. The work item says this was demonstrated on GitLab staging; the vendor advisory does not specify all affected flow conditions. A public AI Gateway source commit dated 2026-09-17 adds a PromptSandboxedEnvironment, describes it as forbidding method access on bound objects, and hardens Jinja operations and filters. That change is mechanically consistent with restricting what a template can do with runtime objects, but the commit does not mention this CVE and public evidence reviewed does not map it to the fixed self-hosted images. Treat it as a separate public hardening change; no public source links it to this CVE or its fixed images.[1], [3], [4], [2]
Self-hosted AI Gateway fixed-release matrix
| Deployment / release line | Fixed AI Gateway release | Operator action |
|---|---|---|
| Self-hosted GitLab 19.2 line | 19.2.4 | Upgrade to the matching self-hosted Gateway image tag and verify the deployed image. |
| Self-hosted GitLab 19.3 line | 19.3.2 | Upgrade to the matching self-hosted Gateway image tag and verify the deployed image. |
| Self-hosted GitLab 19.4 line | 19.4.1 | Upgrade to the matching self-hosted Gateway image tag and verify the deployed image. |
| GitLab-hosted AI Gateway | GitLab states it was already patched. | Confirm the deployment uses GitLab-hosted Gateway; do not apply self-hosted image guidance to this case. |
Step-by-Step Attack Flow
Template execution crosses the sandbox boundary
GitLab work item #1850 reports that a template could call parse_raw() on a LangChain HumanMessage in conversation history, triggering pickle deserialization. This is the reporter’s documented path; do not infer that it covers every affected flow or represents the vendor’s patch.[3]
Source Code: Flaw vs. Secure Implementation
GitLab work item 1850 reports that the vulnerable flow exposed a LangChain HumanMessage object to Jinja, where a template could invoke parse_raw() and trigger pickle deserialization. This defensive example keeps live message objects out of the template context, passes only text data, and denies callable attribute and method access. It illustrates the boundary that must be closed; it is not GitLab’s released patch. GitLab’s separate public commit 89b72644 adds PromptSandboxedEnvironment and filter hardening; its actual title concerns Jinja block-set writes, and no public source links it to CVE-2026-90970 or the fixed images. Operators must follow the vendor’s fixed-release guidance.[3], [2], [6]
Source excerpt: ai_gateway/prompts/base.py (reported vulnerable formatter; hardened design is illustrative)
# Vulnerable-path excerpt reported in GitLab work item 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
# Illustrative defense pattern, not GitLab’s CVE patch.
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)
Engineering & System Hardening Checklist
- ✓Upgrade self-hosted AI Gateway to 19.2.4, 19.3.2, or 19.4.1 on the matching supported GitLab release line; use the matching self-hosted image tag from GitLab's installation guidance.[1], [5]
- ✓Determine whether the deployment uses a self-hosted AI Gateway or GitLab-hosted Gateway. GitLab says GitLab.com, GitLab Dedicated, and self-managed instances using a GitLab-hosted Gateway were already patched.[1]
- ✓Do not treat a prompt-engine sandbox as a complete isolation boundary when templates receive live application objects.[6]
- ✓Pass templates minimal inert data-transfer values; keep methods, deserializers, filesystem/process handles, and service objects out of the template context.[6], [2]
- ✓Apply explicit attribute and callable restrictions, audit built-in filters for direct method calls, and test the exact Jinja version and configuration used in production.[3], [2], [6]
- ✓Add regression tests that assert unsafe object operations fail and legitimate string-only rendering continues to work; never use a real command payload in tests or documentation.[3], [6]
- ✓Do not infer a public exploitation campaign or complete attack chain from the advisory. NVD records the CISA Coordinator SSVC assessment as “exploitation: none” on 2026-10-02; treat that as a dated status, not a claim about later activity.[1], [4]
Sources
- GitLab AI Gateway Critical Patch Release: 19.2.4, 19.3.2, and 19.4.1
- GitLab commit 89b72644 — separate, unlinked hardening: “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