Someone left the back door of the code factory on a spring latch — any developer who clicks a link swings it wide open
CVE-2026-19650 is a cross-site request forgery (CSRF) flaw in GitLab CE/EE's GraphQL multiplex query handler. The root cause is that the /api/graphql endpoint fails to enforce CSRF token validation when mutations are submitted as GET requests through the multiplex path, bypassing the POST+X-CSRF-Token requirement that normally gates state-changing operations. An attacker crafts a malicious URL or page; when an authenticated GitLab user visits it, the browser fires off GraphQL mutations — creating snippets, modifying project settings, or potentially altering CI/CD pipeline configurations — under the victim's session. Affected versions span 18.2 through 18.11.10, 19.0.0–19.0.7, 19.1.0–19.1.5, and 19.2.0–19.2.3. That is roughly 18 months of release trains.
GitLab rates this HIGH / 7.1, which is *directionally correct* but slightly understates the blast radius. The CVSS vector (AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L) captures the social-engineering prerequisite and the integrity impact, but it treats GitLab like a generic web app. In reality, GitLab is the CI/CD control plane: mutations against it can alter .gitlab-ci.yml, inject pipeline steps, exfiltrate CI/CD variables, or modify deploy-key ACLs. That supply-chain multiplier pushes the effective risk above 7.1 for any org running self-managed GitLab with privileged maintainers. noisgate nudges this to 7.5 HIGH.
4 steps from start to impact.
Craft malicious GraphQL multiplex GET URL
/api/graphql with a multiplex query payload containing one or more mutations encoded as GET parameters. Because the multiplex handler does not enforce CSRF token validation on GET-submitted mutations, this payload will be accepted server-side if accompanied by a valid session cookie. No authentication or GitLab account is needed by the attacker.- Knowledge of victim's GitLab instance hostname
- Understanding of the GraphQL multiplex parameter format
- Attacker must know or guess the target GitLab instance URL — trivial for public instances, slightly harder for internal ones
/api/graphql GET requests for mutation keywords (mutation {, Mutation) can flag anomalous traffic.Social-engineer victim into clicking the link
<img> / <iframe> tag on a page the victim will visit. The victim must be authenticated to the target GitLab instance with an active session cookie. No additional credentials are harvested — the browser supplies the session automatically.- Victim must have an active authenticated session on the target GitLab instance
- Victim must click the link or load the page containing the payload
- Requires successful social engineering — conversion rates vary, typically 5–15% in targeted campaigns
- SameSite cookie policies (Lax by default in modern browsers) may block cross-site GET requests carrying cookies for some mutation types, though navigation-level GETs are generally exempt from SameSite=Lax
form-action directives on the attacker's site are irrelevant — this is a direct navigation.Mutations execute under victim's identity
- Victim has sufficient GitLab permissions for the targeted mutation
- Impact is bounded by the victim's role — phishing a Guest-level user yields minimal damage
- GitLab audit logs will record the mutations, enabling post-incident forensics
Supply-chain pivot via CI/CD manipulation
.gitlab-ci.yml include references, inject malicious pipeline steps, or exfiltrate masked CI/CD variables by creating a new pipeline configuration that echoes them. This turns a CSRF into a supply-chain compromise vector — malicious code enters the build pipeline and ships to production.- Victim must be a Maintainer or Owner on a project with active CI/CD pipelines
- CI/CD variables must not be restricted to protected branches only
- Organizations using protected branches and requiring MR approvals add a gate between the mutation and code execution
- Pipeline changes trigger notifications and are visible in merge request diffs
The supporting signals.
| In-the-Wild Exploitation | No confirmed exploitation. Not listed on CISA KEV. No campaign reports from Mandiant, CrowdStrike, or threat intel feeds as of 2026-08-18. |
|---|---|
| Proof-of-Concept | The underlying CSRF-via-GET pattern was publicly demonstrated in HackerOne #1122408 by researcher az3z3l (2021) and expanded by kreep for the multiplex handler variant. The original PoC created snippets; the multiplex variant likely extends to arbitrary mutations. No weaponized exploit toolkit observed. |
| EPSS Score | Not yet scored (disclosed 2026-08-17). Expect initial EPSS in the 0.05–0.15 range given the UI:R requirement and lack of automation-friendly exploitation. |
| KEV Status | Not listed. No CISA KEV entry as of 2026-08-18. |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L — Network-reachable, low complexity, no privileges needed, but requires user interaction. High integrity impact, low availability, no confidentiality impact. The C:N is misleading if mutations can exfiltrate CI/CD variables indirectly. |
| Affected Versions | GitLab CE/EE 18.2 – 18.11.10, 19.0.0 – 19.0.7, 19.1.0 – 19.1.5, 19.2.0 – 19.2.3. ~18 months of releases. |
| Fixed Versions | 18.11.11, 19.0.8, 19.1.6, 19.2.4 (released 2026-08-17). GitLab.com and GitLab Dedicated already patched. |
| Exposure Surface | Censys and Shodan historically show 50,000–80,000 internet-facing self-managed GitLab instances. Many enterprises also run GitLab on internal networks reachable via VPN, expanding the CSRF attack surface to any developer who clicks a link while connected. |
| Disclosure Date | 2026-08-17 via GitLab critical patch release. |
| Reporter | Security researcher kreep via GitLab's HackerOne bug bounty program. |
noisgate verdict.
The single most decisive factor is the role multiplier: GitLab is canonically a CI/CD platform, and CSRF-driven mutation execution against a Maintainer ends in supply-chain compromise — injecting malicious pipeline steps that ship to production. The UI:R prerequisite provides real friction but does not overcome this blast-radius floor.
Why this verdict
- User interaction is real friction but not a blocker. Phishing a GitLab developer or maintainer is well within commodity attacker capability — targeted campaigns against devops teams routinely achieve 5–15% click rates. The UI:R reduces mass exploitation but does not prevent targeted attacks.
- Role multiplier — CI/CD control plane. GitLab is not a generic web app; it is the build and deploy pipeline. Mutations that modify CI/CD variables, inject pipeline steps, or alter deploy keys have supply-chain-scale blast radius. This is the canonical high-value role (CI/CD + artifact stores) from the role catalog, meaning ≥10% of GitLab installs occupy this role by definition — the verdict floor is HIGH.
- Wide affected version range. Spanning 18.2 through 19.2 (~18 months of releases) means most self-managed instances are vulnerable unless they upgraded on patch day. Combined with 50K–80K internet-facing instances, the exposure population is substantial.
- SameSite cookie behavior provides partial mitigation. Modern browsers default to
SameSite=Lax, which blocks cross-site subrequests but permits top-level navigation GETs — exactly the vector this CSRF uses. SameSite=Lax does NOT stop this attack for navigation-triggered GET requests.
Why not higher?
This is not CRITICAL because exploitation requires successful social engineering of an authenticated user — it cannot be automated at scale like a pre-auth RCE. The CVSS scope is Unchanged (S:U), meaning the attacker cannot escape the victim's permission boundary. An attacker who phishes a low-privilege user gets minimal impact. The chain only reaches supply-chain severity when the victim is a Maintainer/Owner, which narrows the high-impact target set.
Why not lower?
GitLab is a CI/CD platform by definition — the supply-chain blast radius when a Maintainer is targeted sets a HIGH floor that friction alone cannot breach. The affected version range is exceptionally wide (18 months), the exposure surface includes tens of thousands of internet-facing instances, and the CSRF pattern (GET-based mutation via multiplex) requires no attacker authentication or complexity. Dropping to MEDIUM would ignore the canonical high-value role of the affected component.
What to do — in priority order.
- Restrict GraphQL API access via reverse proxy — Configure your reverse proxy (nginx, HAProxy, Cloudflare) to block GET requests to
/api/graphqlthat containmutationin query parameters. This surgically kills the CSRF vector without breaking normal GraphQL POST operations. Deploy within the noisgate mitigation SLA of 30 days for HIGH findings. - Enforce SameSite=Strict on GitLab session cookies — Override GitLab's session cookie
SameSiteattribute toStrictat the load balancer or reverse proxy level. This prevents cross-site navigation from sending session cookies, fully neutralizing the CSRF. Note: this will break legitimate cross-site login flows (OAuth redirects) — test thoroughly before deploying. - Scope CI/CD variables to protected branches only — Ensure all sensitive CI/CD variables are marked as *protected* and scoped to protected branches. This limits what a CSRF-driven pipeline injection can exfiltrate, even if the attacker modifies an unprotected branch's
.gitlab-ci.yml. - Enable GitLab audit event streaming to SIEM — Stream GitLab audit events for project settings changes, CI/CD variable modifications, pipeline configuration changes, and deploy key additions to your SIEM. Alert on unusual mutation patterns to catch exploitation attempts post-click.
- Network-level IP allowlisting for GitLab — the CSRF executes from the victim's browser, which is already on the trusted network. IP restrictions do not help because the request originates from an authorized source.
- MFA on GitLab login — MFA protects the authentication ceremony but not an already-authenticated session. Once the user is logged in, the CSRF rides the existing session cookie.
- Content Security Policy on GitLab — CSP on the *GitLab instance* does not prevent a CSRF originating from an *external* attacker-controlled page. CSP would need to be on the attacker's page, which the attacker controls.
Crowdsourced verification payload.
Run this script on any host with curl and network access to your GitLab instance. Invoke as: ./check_cve_2026_19650.sh https://gitlab.example.com. No authentication needed — it checks the GitLab version via the public API.
#!/usr/bin/env bash
# check_cve_2026_19650.sh — Check GitLab instance for CVE-2026-19650
# Usage: ./check_cve_2026_19650.sh <GITLAB_URL>
# Requires: curl, awk
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail
if [ $# -lt 1 ]; then
echo "Usage: $0 <GITLAB_URL>"
exit 2
fi
GITLAB_URL="${1%/}"
# Fetch version from public API (no auth required if version API is exposed)
VERSION=$(curl -sf "${GITLAB_URL}/api/v4/version" 2>/dev/null | grep -oP '"version"\s*:\s*"\K[0-9]+\.[0-9]+\.[0-9]+' || true)
if [ -z "$VERSION" ]; then
# Try metadata endpoint as fallback
VERSION=$(curl -sf "${GITLAB_URL}/api/v4/metadata" 2>/dev/null | grep -oP '"version"\s*:\s*"\K[0-9]+\.[0-9]+\.[0-9]+' || true)
fi
if [ -z "$VERSION" ]; then
echo "UNKNOWN — could not retrieve GitLab version from ${GITLAB_URL}"
echo "The /api/v4/version endpoint may require authentication."
echo "Manually check: Settings > Help > GitLab version"
exit 2
fi
echo "Detected GitLab version: $VERSION"
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
PATCH=$(echo "$VERSION" | cut -d. -f3)
VULNERABLE=0
# Affected: 18.2.0 - 18.11.10
if [ "$MAJOR" -eq 18 ] && [ "$MINOR" -ge 2 ]; then
if [ "$MINOR" -lt 11 ]; then
VULNERABLE=1
elif [ "$MINOR" -eq 11 ] && [ "$PATCH" -lt 11 ]; then
VULNERABLE=1
fi
fi
# Affected: 19.0.0 - 19.0.7
if [ "$MAJOR" -eq 19 ] && [ "$MINOR" -eq 0 ] && [ "$PATCH" -lt 8 ]; then
VULNERABLE=1
fi
# Affected: 19.1.0 - 19.1.5
if [ "$MAJOR" -eq 19 ] && [ "$MINOR" -eq 1 ] && [ "$PATCH" -lt 6 ]; then
VULNERABLE=1
fi
# Affected: 19.2.0 - 19.2.3
if [ "$MAJOR" -eq 19 ] && [ "$MINOR" -eq 2 ] && [ "$PATCH" -lt 4 ]; then
VULNERABLE=1
fi
if [ "$VULNERABLE" -eq 1 ]; then
echo "VULNERABLE — GitLab $VERSION is affected by CVE-2026-19650"
echo "Upgrade to 18.11.11, 19.0.8, 19.1.6, or 19.2.4"
exit 1
else
echo "PATCHED — GitLab $VERSION is not affected by CVE-2026-19650"
exit 0
fiIf you remember one thing.
Sources
- GitLab Critical Patch Release 19.2.4, 19.1.6, 19.0.8, 18.11.11
- The Hacker News — Critical GitLab GraphQL Flaw Coverage
- Tenable — CVE-2026-19650
- CyberPress — GitLab Critical Vulnerability Analysis
- GitLab Issue #324397 — CSRF on /api/graphql via GET Mutations
- CSA Singapore — Multiple Vulnerabilities in GitLab Products
- GitLab Forum — Critical Patch Release Announcement
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.