← Back to Feed CACHED · 2026-10-08 11:49:34 · CACHE_KEY CVE-2026-87719
CVE-2026-87719 · CWE-502 · Disclosed 2026-09-12

GitLab EE Duo Chat GraphQL Subscription Insecure Deserialization Leaking Advanced Search Credentials

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

Like a sticky note with the vault combination on a locked door — you need a badge to get in, but once inside you walk straight to the vault

CVE-2026-87719 is an insecure deserialization flaw in GitLab EE's GraphQL subscription serializer, specifically in the Duo Chat feature (GitLab's AI coding assistant). An authenticated user with Duo Chat access can craft a malicious GraphQL subscription argument that bypasses the server's type validation during deserialization, triggering a server-side object lookup that returns the Advanced Search instance configuration — including the Elasticsearch or OpenSearch cluster address and the credentials GitLab uses to authenticate to it. Affected versions span GitLab EE 18.3.0–18.11.11, 19.0.0–19.0.8, 19.1.0–19.1.7, 19.2.0–19.2.5, and 19.3.0–19.3.1. GitLab CE is not affected.

GitLab scored this 9.9 CRITICAL with CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H — scope-changed, full-triad impact. That's overstated. The direct, demonstrated impact is credential disclosure for a backing service, not arbitrary code execution on the GitLab host. The I:H and A:H values model what an attacker *could* do to the Elasticsearch cluster with stolen credentials, not what the deserialization itself delivers. The vulnerability also requires authentication *and* Duo Chat entitlement, which is gated behind GitLab Premium/Ultimate with Duo enabled — a subset of a subset of the installed base. The 9.9 treats this as if any low-privilege user on any GitLab instance can achieve full-triad impact; in practice, the feature-gating narrows the reachable population and the primary impact is data exposure, not system compromise.

"Duo Chat deserialization leaks Elasticsearch creds — not RCE, but it hands attackers every indexed repo."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Obtain authenticated GitLab EE session

The attacker needs a valid GitLab account on the target instance — a developer, contractor, or any provisioned user. SSO/SAML-federated accounts qualify. If self-registration is enabled, this step is zero-friction. The attacker authenticates via the web UI or API to obtain a session token.
Conditions required:
  • Valid GitLab EE account
  • Network access to GitLab web interface or API
Where this breaks in practice:
  • Most enterprise instances disable self-registration; accounts are provisioned via IdP
  • External attackers need a compromised credential or insider access
Detection/coverage: Standard authentication logs; anomalous login from unusual IP/geo flagged by UEBA.
STEP 02

Confirm Duo Chat entitlement

The attacker's account must have access to GitLab Duo Chat, available on Premium/Ultimate tiers with Duo enabled. The attacker navigates to the Duo Chat interface or queries the GraphQL API to confirm the feature is active. Without Duo Chat, the vulnerable serializer is unreachable.
Conditions required:
  • GitLab EE Premium or Ultimate license
  • Duo Chat enabled at instance level by admin
  • Attacker's role not explicitly excluded from Duo
Where this breaks in practice:
  • Duo Chat is opt-in; not all EE deployments have activated it
  • GitLab's own TEI study profiled 150–250 Duo users per org, suggesting adoption is narrow
  • Enterprises that haven't adopted GitLab AI features are immune
Detection/coverage: Duo Chat usage is logged in GitLab audit events.
STEP 03

Craft malicious GraphQL subscription argument

The attacker constructs a specially crafted GraphQL subscription mutation targeting the Duo Chat endpoint. The payload bypasses the serialization type-checking in the GraphQL subscription serializer, triggering an insecure deserialization path that performs a server-side object lookup instead of normal processing. No public PoC exists; the attacker must reverse-engineer the bypass from GitLab EE source or discover it independently.
Conditions required:
  • Knowledge of the vulnerable GraphQL endpoint and deserialization bypass
  • Ability to send raw GraphQL requests (curl, GraphQL client, browser DevTools)
Where this breaks in practice:
  • No public PoC as of 2026-10-08
  • Attacker must reverse-engineer from EE source (available to licensees) or independent discovery
  • Non-trivial but achievable for a skilled adversary
Detection/coverage: WAF rules inspecting GraphQL payloads for unusual subscription arguments; GitLab application logs may record malformed subscription attempts.
STEP 04

Retrieve Advanced Search credentials

The deserialization bypass returns the Advanced Search instance configuration object containing the Elasticsearch/OpenSearch cluster URL and authentication credentials (username/password or API key). The attacker now possesses direct credentials to the backing search infrastructure. This is a single-request exploit — one crafted subscription returns the full config.
Conditions required:
  • Advanced Search must be configured on the target instance
  • Separate Elasticsearch/OpenSearch cluster provisioned and connected to GitLab
Where this breaks in practice:
  • Not all EE instances use Advanced Search — requires provisioning a separate search cluster
  • Smaller EE deployments often skip it
  • Without Advanced Search configured, the lookup returns nothing actionable
Detection/coverage: No network-level indicator; the response is a normal GraphQL reply. Detection depends on application-layer audit logging of the subscription serializer path.
STEP 05

Access Elasticsearch/OpenSearch cluster directly

Using the stolen credentials, the attacker connects to the search cluster directly, bypassing GitLab's application-layer RBAC, logging, and access controls. The cluster contains indexed copies of all GitLab content: source code, issues, merge requests, wiki pages, and comments. The attacker can use Elasticsearch APIs (_search, _cat/indices) to enumerate and exfiltrate all indexed data, including secrets embedded in source code.
Conditions required:
  • Network reachability from attacker's position to the Elasticsearch/OpenSearch endpoint
  • Stolen credentials still valid and not yet rotated
Where this breaks in practice:
  • Well-architected deployments place the search cluster on an internal network segment
  • Attacker may need to pivot from the GitLab host or adjacent network
  • Network segmentation is a real barrier; the credential alone is insufficient without reachability
Detection/coverage: Elasticsearch audit logging (if enabled) shows logins from non-GitLab IPs; network monitoring detects unexpected connections to port 9200/9243.
03 · Compensating Control

1
HIGH 7.8→IGNORE 0.0
SEVERITY REDUCED
Upgrade GitLab EE to a patched version — Deploy 19.3.2, 19.2.6, 19.1.8, 19.0.9, or 18.11.12. This eliminates the deserialization bypass entirely. Deploy within 30 days per the noisgate mitigation SLA for HIGH.
2
HIGH 7.8→MEDIUM 5.5
SEVERITY REDUCED
Rotate Advanced Search credentials — Change the Elasticsearch/OpenSearch credentials in gitlab.rb or Kubernetes secrets, reconfigure GitLab, and verify indexing resumes. Audit the search cluster's access logs for connections from non-GitLab IPs since 2026-09-12. Deploy within 30 days. Closes the window if credentials were already exfiltrated.
3
HIGH 7.8→LOW 2.0
SEVERITY REDUCED
Disable Duo Chat temporarily — Set gitlab_duo_chat_enabled: false at the instance level (Admin → Settings → AI/ML). Removes the attack's entry point by making the vulnerable serializer unreachable. Deploy within 30 days as a pre-patch mitigation if upgrade is delayed.
4
HIGH 7.8→MEDIUM 5.0
SEVERITY REDUCED
Network-segment the Elasticsearch/OpenSearch cluster — Restrict inbound connections to the search cluster to only GitLab application server IPs via firewall rules or security groups. Block all other sources. Prevents the attacker from using stolen credentials even if obtained. Deploy within 30 days.
5
HIGH 7.8→MEDIUM 6.0
SEVERITY REDUCED
Deploy WAF rules on the GitLab GraphQL endpoint — Add rules to inspect and block anomalous GraphQL subscription payloads targeting the Duo Chat serializer. Defense-in-depth; bypass is possible but raises the bar. Deploy within 30 days.
What doesn't work
  • GitLab RBAC / project-level permissions — the vulnerability bypasses application-layer access controls entirely; restricting project visibility does not prevent the deserialization from returning the search cluster config object.
  • Elasticsearch RBAC — the leaked credentials are the ones GitLab itself uses to authenticate, which typically have cluster-wide read/write access to all indexes. ES RBAC only helps if you've created a separate, least-privilege service account for GitLab (rare in practice).
  • GraphQL rate limiting — the attack requires a single request; rate limiting does not prevent a one-shot exploit.
04 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNo confirmed exploitation. Not listed in CISA KEV. No campaigns attributed.
Proof-of-conceptNo public PoC as of 2026-10-08. Not indexed on pocindex.io. No GitHub repos named CVE-2026-87719. No ExploitDB, nuclei, or Metasploit modules. Discovered via HackerOne bug bounty by researcher kyyblin.
EPSS0.00571 (0.571%) — low exploitation probability, well below the 90th-percentile threshold.
KEV statusNot listed.
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H — network-accessible, low complexity, low privilege, scope-changed. The S:C and full-triad I/A model downstream Elasticsearch impact, not direct RCE.
Affected versionsGitLab EE 18.3.0–18.11.11, 19.0.0–19.0.8, 19.1.0–19.1.7, 19.2.0–19.2.5, 19.3.0–19.3.1. GitLab CE is not affected.
Fixed versions18.11.12, 19.0.9, 19.1.8, 19.2.6, 19.3.2 (initial patch 2026-09-10; backports 2026-09-23).
Exposure data~86,000 GitLab instances visible on Shodan/Censys (per CVE-2026-85706 scan data, Sep 2026). The subset running EE with Duo Chat enabled and Advanced Search configured is materially smaller — likely low thousands.
Disclosure2026-09-12 (NVD publication 2026-09-25). Researcher: kyyblin via HackerOne.
Prerequisite featuresRequires both Duo Chat (Premium/Ultimate + Duo add-on) and Advanced Search (Premium/Ultimate + external Elasticsearch/OpenSearch cluster).

Sources.

  1. GitLab Critical Patch Release 19.3.2, 19.2.6, 19.1.8
  2. CVE-2026-87719 — Strix.ai Analysis
  3. CVE-2026-87719 — OpenCVE
  4. CVE-2026-87719 — OSV (Open Source Vulnerabilities)
  5. CVE-2026-87719 — Vulners
  6. What Discloses From GitLab EE CVE-2026-87719 — DEV Community
  7. GitLab Advanced Search Elasticsearch Integration
  8. GitLab Duo Agent Platform GA Announcement
05 · The Call

Final Verdict
↓ DOWNGRADED to HIGH (7.8/10)

Why this verdict

  • Impact is credential disclosure, not RCE. The deserialization bypass retrieves stored Elasticsearch/OpenSearch configuration and credentials. It does not achieve code execution, write to the GitLab database, or modify repositories. The vendor's I:H and A:H model downstream impact on the search cluster — real but indirect, requiring a second hop.
  • Double feature-gating narrows the reachable population. The attacker needs (a) Duo Chat enabled and (b) Advanced Search configured. Duo Chat requires Premium/Ultimate plus the Duo add-on. Advanced Search requires a separately provisioned Elasticsearch/OpenSearch cluster. Enterprises without either feature are immune. This narrows the vulnerable population well below the ~86K visible GitLab instances.
  • Network segmentation limits lateral reach. Even with valid credentials, the attacker must reach the Elasticsearch endpoint. Well-architected deployments isolate the search cluster on an internal segment. The credential alone is insufficient without reachability.
  • No PoC, no exploitation, low EPSS. EPSS of 0.57%, no known campaigns, and no public exploit code mean current real-world risk is theoretical and limited to skilled adversaries.
  • Role multiplier: GitLab EE is a canonical CI/CD and source-code platform (high-value role). The Elasticsearch cluster indexes *all* repository content, issues, merge requests, and wiki pages. Successful exploitation yields mass data egress of source code and potentially embedded secrets. On instances where Advanced Search is configured, the blast radius is supply-chain-scale — the attacker reads every indexed repo without GitLab RBAC enforcement. This sets a floor of HIGH regardless of friction. The chain succeeds in this role; the outcome is mass data egress, not merely a single-host compromise.

Why not higher?

The chain does not deliver RCE or direct write access to GitLab itself. The impact requires a secondary hop — using stolen credentials against a separate service — that network segmentation may block entirely. The double feature-gating (Duo Chat + Advanced Search) means the vulnerable population is a subset of EE deployments, not the full installed base. If this were unauthenticated RCE on the GitLab host, it would stay CRITICAL.

Why not lower?

GitLab is a canonical supply-chain component. The disclosed credentials bypass all application-layer access controls on the search index, and a single Elasticsearch API call exfiltrates every indexed source file, issue, and merge request. Authentication is low (any developer account), complexity is low, and no user interaction is needed. The supply-chain blast radius on deployments that run Advanced Search is too severe to drop below HIGH.

06 · Verification

Crowdsourced verification payload.

Run on the GitLab host (or any host with SSH/API access). Invoke with: bash check_cve_2026_87719.sh https://gitlab.example.com or bash check_cve_2026_87719.sh (auto-detects local Omnibus). Requires curl and either local file access to /opt/gitlab/version-manifest.txt or network access to the /api/v4/version endpoint. No elevated privileges needed.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_87719.sh — Detect CVE-2026-87719 (GitLab EE deserialization)
# Usage: bash check_cve_2026_87719.sh [GITLAB_URL]
# Exit codes: 1=VULNERABLE, 0=PATCHED, 2=UNKNOWN

set -euo pipefail

GITLAB_URL="${1:-}"
VERSION=""
EDITION=""

# Try local Omnibus version file first
if [[ -f /opt/gitlab/version-manifest.txt ]]; then
  VERSION=$(head -1 /opt/gitlab/version-manifest.txt | awk '{print $2}')
  if grep -qi 'gitlab-ee' /opt/gitlab/version-manifest.txt 2>/dev/null; then
    EDITION="ee"
  else
    EDITION="ce"
  fi
fi

# Fall back to API
if [[ -z "$VERSION" && -n "$GITLAB_URL" ]]; then
  API_RESP=$(curl -sf "${GITLAB_URL}/api/v4/version" 2>/dev/null || true)
  if [[ -n "$API_RESP" ]]; then
    VERSION=$(echo "$API_RESP" | grep -oP '"version"\s*:\s*"\K[^"]+' || true)
    if echo "$API_RESP" | grep -qi 'enterprise'; then
      EDITION="ee"
    else
      EDITION="ce"
    fi
  fi
fi

if [[ -z "$VERSION" ]]; then
  echo "UNKNOWN — could not determine GitLab version."
  echo "Provide the GitLab URL as argument or run on the GitLab host."
  exit 2
fi

echo "Detected GitLab $VERSION ($EDITION)"

# CE is not affected
if [[ "$EDITION" == "ce" ]]; then
  echo "PATCHED — GitLab CE is not affected by CVE-2026-87719."
  exit 0
fi

# Parse version components
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
PATCH=$(echo "$VERSION" | cut -d. -f3 | sed 's/[^0-9].*//')

vulnerable=false

# Not affected if before 18.3
if [[ $MAJOR -lt 18 ]] || { [[ $MAJOR -eq 18 ]] && [[ $MINOR -lt 3 ]]; }; then
  echo "PATCHED — version predates the vulnerable code (introduced in 18.3)."
  exit 0
fi

# 18.3.0 - 18.11.x: fixed in 18.11.12
if [[ $MAJOR -eq 18 ]]; then
  if [[ $MINOR -lt 11 ]]; then
    vulnerable=true
  elif [[ $MINOR -eq 11 ]] && [[ $PATCH -lt 12 ]]; then
    vulnerable=true
  fi
fi

# 19.0.x: fixed in 19.0.9
if [[ $MAJOR -eq 19 ]] && [[ $MINOR -eq 0 ]] && [[ $PATCH -lt 9 ]]; then
  vulnerable=true
fi

# 19.1.x: fixed in 19.1.8
if [[ $MAJOR -eq 19 ]] && [[ $MINOR -eq 1 ]] && [[ $PATCH -lt 8 ]]; then
  vulnerable=true
fi

# 19.2.x: fixed in 19.2.6
if [[ $MAJOR -eq 19 ]] && [[ $MINOR -eq 2 ]] && [[ $PATCH -lt 6 ]]; then
  vulnerable=true
fi

# 19.3.x: fixed in 19.3.2
if [[ $MAJOR -eq 19 ]] && [[ $MINOR -eq 3 ]] && [[ $PATCH -lt 2 ]]; then
  vulnerable=true
fi

if $vulnerable; then
  echo "VULNERABLE — GitLab EE $VERSION is affected by CVE-2026-87719."
  echo "Upgrade to 18.11.12 / 19.0.9 / 19.1.8 / 19.2.6 / 19.3.2 or later."
  exit 1
else
  echo "PATCHED — GitLab EE $VERSION is not affected."
  exit 0
fi