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.
5 steps from start to impact.
Obtain authenticated GitLab EE session
- Valid GitLab EE account
- Network access to GitLab web interface or API
- Most enterprise instances disable self-registration; accounts are provisioned via IdP
- External attackers need a compromised credential or insider access
Confirm Duo Chat entitlement
- GitLab EE Premium or Ultimate license
- Duo Chat enabled at instance level by admin
- Attacker's role not explicitly excluded from Duo
- 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
Craft malicious GraphQL subscription argument
- Knowledge of the vulnerable GraphQL endpoint and deserialization bypass
- Ability to send raw GraphQL requests (curl, GraphQL client, browser DevTools)
- 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
Retrieve Advanced Search credentials
- Advanced Search must be configured on the target instance
- Separate Elasticsearch/OpenSearch cluster provisioned and connected to GitLab
- 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
Access Elasticsearch/OpenSearch cluster directly
_search, _cat/indices) to enumerate and exfiltrate all indexed data, including secrets embedded in source code.- Network reachability from attacker's position to the Elasticsearch/OpenSearch endpoint
- Stolen credentials still valid and not yet rotated
- 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
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.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.- 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.
The supporting signals.
| In-the-wild exploitation | No confirmed exploitation. Not listed in CISA KEV. No campaigns attributed. |
|---|---|
| Proof-of-concept | No 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. |
| EPSS | 0.00571 (0.571%) — low exploitation probability, well below the 90th-percentile threshold. |
| KEV status | Not listed. |
| CVSS vector | CVSS: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 versions | 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, 19.3.0–19.3.1. GitLab CE is not affected. |
| Fixed versions | 18.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. |
| Disclosure | 2026-09-12 (NVD publication 2026-09-25). Researcher: kyyblin via HackerOne. |
| Prerequisite features | Requires both Duo Chat (Premium/Ultimate + Duo add-on) and Advanced Search (Premium/Ultimate + external Elasticsearch/OpenSearch cluster). |
Sources.
- GitLab Critical Patch Release 19.3.2, 19.2.6, 19.1.8
- CVE-2026-87719 — Strix.ai Analysis
- CVE-2026-87719 — OpenCVE
- CVE-2026-87719 — OSV (Open Source Vulnerabilities)
- CVE-2026-87719 — Vulners
- What Discloses From GitLab EE CVE-2026-87719 — DEV Community
- GitLab Advanced Search Elasticsearch Integration
- GitLab Duo Agent Platform GA Announcement
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.
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.
#!/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