← Back to Feed CACHED · 2026-10-03 01:15:16 · CACHE_KEY CVE-2026-90970
CVE-2026-90970 · CWE-1336 · Disclosed 2026-10-02

GitLab has remediated a vulnerability in the GitLab AI Gateway component affecting all versions of the AI…

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

A Jinja2 sandbox escape in GitLab's AI plumbing that turns a developer's prompt into root on the gateway box

CVE-2026-90970 is a server-side template injection (SSTI) flaw in the GitLab AI Gateway's Duo Workflow Service. An authenticated user who has been granted Duo Agent Platform access can craft a malicious flow configuration that breaks out of the prompt template sandbox — almost certainly a Jinja2 SandboxedEnvironment bypass — and gains arbitrary command execution on the AI Gateway host. Affected versions span AI Gateway 18.1.6 through 19.2.3, 19.3.0–19.3.1, and 19.4.0; fixed in 19.2.4, 19.3.2, and 19.4.1. Critically, only self-hosted AI Gateway deployments are vulnerable — organizations using GitLab.com, GitLab Dedicated, or the cloud-hosted AI Gateway are already patched by GitLab.

GitLab rates this CVSS 9.9 CRITICAL with Scope:Changed (sandbox → OS), and the technical chain — low-privilege auth, no user interaction, network-reachable, full CIA impact — justifies that vector *on paper*. In practice, the vendor score overstates urgency for most defenders. The vulnerability requires two gates: a valid GitLab account *and* admin-granted Duo Agent Platform access, which is a Premium/Ultimate add-on feature that many orgs have not yet rolled out. The affected population — self-hosted AI Gateway operators — is a small subset of an already smaller subset (self-managed GitLab customers who opted into self-hosting the AI proxy). This is the second SSTI in the same component in eight months (CVE-2026-1868 was patched in February), signaling a systemic template-handling weakness in the Duo Workflow Service that deserves architectural attention, but the real-world blast radius is narrower than the score implies.

"Self-hosted AI Gateway SSTI is real RCE but gated behind Duo access and a small install base"
02 · The Attack Path

4 steps from start to impact.

STEP 01

Authenticate to GitLab with Duo Agent Platform access

The attacker logs into a GitLab instance (self-managed or SaaS — but only self-hosted AI Gateways are vulnerable downstream) using valid credentials for an account that has been granted Duo Agent Platform access by a namespace administrator. This is not a default permission; it requires an admin to enable Duo Agent Platform features for the user's namespace and the user to hold a GitLab Premium or Ultimate seat with Duo Enterprise add-on.
Conditions required:
  • Valid GitLab user credentials
  • Duo Agent Platform access granted by admin
  • Target org runs self-hosted AI Gateway (not cloud-hosted)
Where this breaks in practice:
  • Duo Agent Platform is an opt-in feature requiring admin enablement — many orgs have not rolled it out
  • Self-hosted AI Gateway is a minority deployment model; most GitLab customers use the cloud-hosted gateway
  • Requires insider access or compromised credentials as a starting position
Detection/coverage: GitLab audit logs capture authentication events and Duo Agent Platform feature usage; SIEM correlation on unusual flow-creation activity from new or anomalous accounts
STEP 02

Craft a malicious Duo Agent Platform flow configuration

The attacker creates a custom flow definition through the Duo Agent Platform's flow editor. The flow's prompt template field is injected with a Jinja2 SSTI payload designed to escape the SandboxedEnvironment. Common Jinja2 sandbox escapes use __subclasses__() traversal to reach os.popen() or subprocess.Popen. The payload is embedded within what appears to be a legitimate AI prompt template.
Conditions required:
  • Knowledge of Jinja2 SSTI sandbox escape techniques
  • Access to flow configuration interface
Where this breaks in practice:
  • No public PoC exists as of 2026-10-03, though Jinja2 SSTI is a well-documented attack class with abundant references
  • The specific sandbox implementation may require targeted research to bypass — generic payloads may not work if GitLab added custom restrictions
Detection/coverage: Application-layer WAF rules for SSTI payloads (e.g., {{, {%, __class__, __subclasses__) in flow definition fields; GitLab application logs for flow creation/modification events
STEP 03

Submit the flow to trigger template rendering on the AI Gateway

When the crafted flow is executed, the AI Gateway's Duo Workflow Service processes the prompt template. The template engine evaluates the injected payload outside the sandbox boundary, executing the attacker's commands as the process user on the AI Gateway host. The CVSS Scope:Changed reflects this sandbox-to-OS boundary crossing.
Conditions required:
  • AI Gateway processes the flow without additional input validation
  • Template engine evaluates the payload server-side
Where this breaks in practice:
  • The gateway service likely runs as a non-root container user in Docker/Kubernetes deployments, limiting initial privilege
  • Container isolation (if properly configured) constrains lateral movement from the gateway host
Detection/coverage: Runtime security tools (Falco, Sysdig, Aqua) detecting unexpected process execution inside the AI Gateway container; network monitoring for anomalous outbound connections from the gateway
STEP 04

Exploit RCE for credential theft and lateral movement

With command execution on the AI Gateway host, the attacker can harvest AI model provider API keys and credentials configured on the gateway, intercept code and prompts transiting through the service, and attempt lateral movement to the GitLab instance or other internal infrastructure. In a supply-chain attack scenario, the attacker could modify AI-generated code suggestions in transit to introduce backdoors.
Conditions required:
  • Gateway host stores model API credentials (environment variables or config files)
  • Network connectivity from gateway to other internal services
Where this breaks in practice:
  • Container network policies and segmentation may block lateral movement
  • Model API keys alone don't provide access to the GitLab instance or source code repos directly
  • Modifying AI responses in transit requires deep understanding of the gateway's internal request pipeline
Detection/coverage: Cloud provider credential-usage monitoring for API keys; anomalous model API call patterns; code review processes catching AI-suggested backdoors; container escape detection via node-level EDR
03 · Compensating Control

1
HIGH 8.5→IGNORE 0.0
SEVERITY REDUCED
Upgrade the AI Gateway to 19.2.4, 19.3.2, or 19.4.1 immediately — The definitive fix. Self-hosted AI Gateway deployments should pull the patched container image (gitlab/model-gateway with the corresponding version tag) and redeploy. Per the noisgate mitigation SLA for HIGH severity, deploy within 30 days; given zero-day proximity and the CI/CD role, aim for 7 days if possible.
2
HIGH 8.5→MEDIUM 5.5
SEVERITY REDUCED
Restrict Duo Agent Platform access to essential users only — Audit namespace-level Duo Agent Platform access grants. Remove access from users who do not actively need custom flow creation. This breaks the attack chain at the privilege gate. Under the composite identity model, the @duo-developer service account limits actions to the intersection of user and service account permissions — ensure this is properly scoped. Deploy within 30 days per the noisgate mitigation SLA.
3
HIGH 8.5→HIGH 7.5
Enforce container runtime security on the AI Gateway pod — Deploy a runtime security tool (Falco, Sysdig Secure, or equivalent) configured to alert on unexpected process execution, shell spawning, and outbound network connections from the AI Gateway container. This detects exploitation but does not prevent it. Also ensure the container runs as a non-root user and uses a read-only root filesystem. Deploy within 30 days per the noisgate mitigation SLA.
4
HIGH 8.5→MEDIUM 6.0
SEVERITY REDUCED
Segment the AI Gateway network to prevent lateral movement — Apply network policies (Kubernetes NetworkPolicy or firewall rules) restricting the AI Gateway container's egress to only the required AI model provider endpoints and the GitLab instance API. Block all other outbound connectivity. This limits post-exploitation blast radius even if RCE succeeds. Deploy within 30 days.
5
HIGH 8.5→HIGH 8.0
Rotate AI model provider API keys after patching — After upgrading, rotate all API keys and credentials configured on the AI Gateway (OpenAI, Anthropic, Azure OpenAI, AWS Bedrock, etc.) in case they were already exfiltrated. Check model provider dashboards for anomalous usage patterns.
What doesn't work
  • GitLab instance-level patching alone does not help — the vulnerability is in the AI Gateway service, which is a separate deployment artifact (gitlab/model-gateway Docker image). Patching GitLab CE/EE without updating the AI Gateway container leaves you exposed.
  • WAF rules for SSTI patterns are a partial mitigation at best — the flow configuration payload is submitted through GitLab's authenticated API, and template injection payloads can be obfuscated to bypass pattern-matching WAF rules. Do not rely on this as a primary control.
  • Disabling GitLab Duo entirely would work but is operationally disruptive — if you've deployed self-hosted AI Gateway, your development teams likely depend on Duo features. Restricting Agent Platform access (not all Duo features) is the targeted control.
04 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone confirmed. GitLab's advisory does not mention active exploitation. CISA assessed exploitation status as "none." Not listed in CISA KEV catalog as of 2026-10-03.
Proof-of-concept availabilityNo public PoC exists. Checked pocindex.io, GitHub (no repos named CVE-2026-90970), ExploitDB — all negative. However, Jinja2 SSTI sandbox escapes are a well-documented attack class with extensive public literature (e.g., HackTricks SSTI), so weaponization by a skilled attacker is realistic within days to weeks.
EPSS scoreNot yet scored. CVE was published 2026-10-02; EPSS model has not yet computed a prediction. Expect initial score within 7–14 days.
CVSS vector interpretationCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H — Network-reachable, low complexity, low privilege, no user interaction, scope change (sandbox → OS), full CIA impact. The S:C is technically correct (template sandbox to host OS) but inflates the score; the PR:L underrepresents the *effective* privilege gate (Duo Agent Platform access is narrower than generic low-privilege auth).
Affected versionsAI Gateway 18.1.6 through 19.2.3, 19.3.0–19.3.1, 19.4.0
Fixed versionsAI Gateway 19.2.4, 19.3.2, 19.4.1. GitLab CE/EE also patched in 19.2.4, 19.1.6, 19.0.8, 18.11.11.
Prior artCVE-2026-1868 (Feb 2026, CVSS 9.9) — same CWE-1336, same component (Duo Workflow Service), same attack class. Second SSTI in the AI Gateway in eight months signals a systemic template-handling weakness.
Scanning / exposure dataNo GreyNoise or Shodan tags specific to this CVE. Self-hosted AI Gateway instances are typically not directly internet-facing — they sit behind the GitLab instance and are accessed internally. Shodan/Censys exposure is expected to be minimal.
Researcher / disclosureReported by HackerOne user invisiblemeerkat via GitLab's bug bounty program. GitLab work item #628842. Coordinated disclosure on 2026-10-02.
Scope limitationOnly self-hosted AI Gateway deployments are affected. GitLab.com, GitLab Dedicated, and Self-Managed instances using the cloud-hosted AI Gateway are already patched. The vulnerable population is limited to orgs that explicitly chose to self-host their AI Gateway — primarily regulated industries (financial services, government, healthcare) with data residency requirements.

Sources.

  1. The Hacker News — GitLab Patches Critical 9.9 AI Gateway Flaw
  2. SecurityOnline — GitLab AI Gateway Vulnerability CVE-2026-90970 Patched
  3. The Hacker Wire — CVE-2026-90970: SSTI Analysis & Fix
  4. SQ Magazine — GitLab AI Gateway Critical RCE CVE-2026-90970
  5. GitLab Docs — AI Gateway Configuration
  6. GitLab Docs — Duo Agent Platform Security
  7. GitLab Work Item #628842
  8. Strix.ai — CVE-2026-90970 Intelligence
05 · The Call

Final Verdict
↓ DOWNGRADED to HIGH (8.5/10)

Why this verdict

  • Gate 1 — authentication + Duo Agent Platform access: The CVSS PR:L tag understates the real prerequisite. Exploiting this requires not just any GitLab account but one with admin-granted Duo Agent Platform access, which is a Premium/Ultimate add-on feature. This is meaningfully narrower than 'any low-privilege user' and implies either insider threat or post-compromise credential theft with specific privilege requirements.
  • Gate 2 — self-hosted AI Gateway only: The vast majority of GitLab customers use the cloud-hosted AI Gateway (patched by GitLab centrally). Self-hosted AI Gateway is an opt-in deployment model chosen primarily by regulated enterprises. This reduces the vulnerable population to a small fraction of the GitLab installed base.
  • No exploitation evidence or PoC: Disclosed 2026-10-02, no public PoC, no KEV listing, no confirmed exploitation. While Jinja2 SSTI is a known technique, the specific sandbox implementation requires targeted research. The urgency clock is ticking but has not yet reached the exploitation threshold.
  • Role multiplier: The GitLab AI Gateway occupies a CI/CD-adjacent high-value role by definition — 100% of self-hosted AI Gateway installs serve CI/CD environments. RCE on the gateway enables: (1) theft of AI model provider API keys/credentials, (2) interception of code and prompts transiting the gateway, (3) potential supply-chain attack by modifying AI-generated code suggestions in transit, (4) lateral movement toward the GitLab instance. The blast radius at the high-value role is host → tenant → potentially supply-chain if the attacker modifies code suggestions. This sets a floor of HIGH. However, the AI Gateway is an *auxiliary AI proxy service*, not the core GitLab CI/CD engine (Runner, pipelines, registry). Supply-chain pivot requires additional steps beyond the initial RCE (understanding the gateway internals, intercepting the right request pipeline). The floor is HIGH, not CRITICAL.
  • Scope:Changed inflation: The S:C in the CVSS vector reflects the sandbox → OS boundary crossing, which adds ~0.5 points. This is technically accurate but the 'sandbox' here is a template engine sandbox within a service that itself runs in a container — it's a scope change within a scope change, which the CVSS model doesn't distinguish from escaping a VM. Real-world impact is closer to an 8.5 than a 9.9.

Why not higher?

The floor for a canonical CI/CD component would be CRITICAL, but the AI Gateway is not the core CI/CD engine — it's an AI model proxy service. The supply-chain attack path (modifying AI code suggestions in transit) is plausible but requires significant additional effort beyond the initial RCE, and depends on developers blindly accepting AI-generated code. Upgrading to CRITICAL would require either confirmed exploitation, a public PoC demonstrating trivial weaponization, or evidence that the gateway runs with privileges sufficient to directly compromise the GitLab instance.

Why not lower?

Despite the friction, this is still unauthenticated-from-the-gateway's-perspective RCE on a service that handles sensitive code and AI model credentials. The insider-threat angle is real: a disgruntled developer with Duo access could pivot from 'I can write code' to 'I own the AI infrastructure.' The second SSTI in eight months in the same component suggests the underlying template handling is brittle and more bypasses may follow. Self-hosted AI Gateway deployments are concentrated in exactly the regulated, high-value environments where a compromise matters most.

06 · Verification

Crowdsourced verification payload.

Run this script on the host running the AI Gateway container (or any host with Docker/kubectl access to inspect the gateway deployment). Execute as: bash check_cve_2026_90970.sh. Requires docker or kubectl CLI access. No elevated privileges needed beyond container inspection permissions.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_90970.sh
# Checks whether the GitLab AI Gateway is vulnerable to CVE-2026-90970
# (SSTI sandbox escape in Duo Workflow Service)
# Outputs: VULNERABLE / PATCHED / UNKNOWN

set -euo pipefail

CVE="CVE-2026-90970"
COMPONENT="GitLab AI Gateway"

# Fixed versions
FIXED_192="19.2.4"
FIXED_193="19.3.2"
FIXED_194="19.4.1"
FIRST_VULN="18.1.6"

version_gte() {
  # Returns 0 if $1 >= $2 (semver comparison)
  printf '%s\n%s' "$2" "$1" | sort -t. -k1,1n -k2,2n -k3,3n -C 2>/dev/null
}

check_version() {
  local ver="$1"
  local major minor patch
  IFS='.' read -r major minor patch <<< "$ver"
  patch=${patch:-0}

  # Check if version is below the first vulnerable version
  if ! version_gte "$ver" "$FIRST_VULN"; then
    echo "PATCHED"
    echo "INFO: $COMPONENT version $ver is below the vulnerable range ($FIRST_VULN+)."
    return
  fi

  # Check 19.4.x branch
  if [[ "$major" -eq 19 && "$minor" -eq 4 ]]; then
    if version_gte "$ver" "$FIXED_194"; then
      echo "PATCHED"
      echo "INFO: $COMPONENT version $ver >= $FIXED_194 (fixed)."
    else
      echo "VULNERABLE"
      echo "DETAIL: $COMPONENT version $ver is vulnerable. Upgrade to >= $FIXED_194."
    fi
    return
  fi

  # Check 19.3.x branch
  if [[ "$major" -eq 19 && "$minor" -eq 3 ]]; then
    if version_gte "$ver" "$FIXED_193"; then
      echo "PATCHED"
      echo "INFO: $COMPONENT version $ver >= $FIXED_193 (fixed)."
    else
      echo "VULNERABLE"
      echo "DETAIL: $COMPONENT version $ver is vulnerable. Upgrade to >= $FIXED_193."
    fi
    return
  fi

  # Check 18.1.6 through 19.2.x branch
  if version_gte "$ver" "$FIRST_VULN"; then
    if [[ "$major" -eq 19 && "$minor" -le 2 ]]; then
      if version_gte "$ver" "$FIXED_192"; then
        echo "PATCHED"
        echo "INFO: $COMPONENT version $ver >= $FIXED_192 (fixed)."
      else
        echo "VULNERABLE"
        echo "DETAIL: $COMPONENT version $ver is vulnerable. Upgrade to >= $FIXED_192."
      fi
    elif [[ "$major" -lt 19 ]]; then
      echo "VULNERABLE"
      echo "DETAIL: $COMPONENT version $ver is in the vulnerable range. Upgrade to >= $FIXED_192."
    else
      # 19.5+ assumed patched
      echo "PATCHED"
      echo "INFO: $COMPONENT version $ver is beyond the affected range."
    fi
    return
  fi
}

echo "=== $CVE Vulnerability Check ==="
echo "Component: $COMPONENT"
echo "Checking for AI Gateway container..."
echo ""

GATEWAY_VERSION=""

# Method 1: Docker
if command -v docker &>/dev/null; then
  CONTAINER_ID=$(docker ps --filter "ancestor=gitlab/model-gateway" --format "{{.ID}}" 2>/dev/null | head -1)
  if [[ -z "$CONTAINER_ID" ]]; then
    CONTAINER_ID=$(docker ps --filter "name=ai-gateway" --format "{{.ID}}" 2>/dev/null | head -1)
  fi
  if [[ -n "$CONTAINER_ID" ]]; then
    IMAGE=$(docker inspect --format '{{.Config.Image}}' "$CONTAINER_ID" 2>/dev/null)
    # Extract version from image tag (e.g., self-hosted-v19.2.3-ee or 19.2.3)
    GATEWAY_VERSION=$(echo "$IMAGE" | grep -oP '\d+\.\d+\.\d+' | head -1)
    echo "Found Docker container: $CONTAINER_ID"
    echo "Image: $IMAGE"
  fi
fi

# Method 2: Kubernetes
if [[ -z "$GATEWAY_VERSION" ]] && command -v kubectl &>/dev/null; then
  POD=$(kubectl get pods --all-namespaces -l app=ai-gateway -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
  if [[ -z "$POD" ]]; then
    POD=$(kubectl get pods --all-namespaces -l app.kubernetes.io/name=ai-gateway -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
  fi
  if [[ -n "$POD" ]]; then
    NS=$(kubectl get pods --all-namespaces -l app=ai-gateway -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true)
    IMAGE=$(kubectl get pod "$POD" -n "${NS:-default}" -o jsonpath='{.spec.containers[0].image}' 2>/dev/null || true)
    GATEWAY_VERSION=$(echo "$IMAGE" | grep -oP '\d+\.\d+\.\d+' | head -1)
    echo "Found Kubernetes pod: $POD (namespace: ${NS:-default})"
    echo "Image: $IMAGE"
  fi
fi

echo ""

if [[ -z "$GATEWAY_VERSION" ]]; then
  echo "UNKNOWN"
  echo "Could not detect a running GitLab AI Gateway container."
  echo "If you know the version, re-run with: VERSION=19.2.3 $0"
  # Allow manual override
  if [[ -n "${VERSION:-}" ]]; then
    GATEWAY_VERSION="$VERSION"
    echo ""
    echo "Using manually specified version: $GATEWAY_VERSION"
    check_version "$GATEWAY_VERSION"
  fi
  exit 2
fi

echo "Detected version: $GATEWAY_VERSION"
echo ""
check_version "$GATEWAY_VERSION"
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously