GitLab GraphQL Code Injection — CVE-2026-19478
Executive Summary
CVE-2026-19478 (CVSS 3.1: 9.4 Critical, CWE-94 — Improper Control of Generation of Code) is an unauthenticated code-injection flaw in the GraphQL layer of self-managed GitLab Community and Enterprise Edition. GitLab shipped an out-of-band critical patch release on 2026-08-17 (versions 19.2.4, 19.1.6, 19.0.8, 18.11.11) after a HackerOne researcher (hiimguardian) reported that a GraphQL directive could be abused, without any credentials, user interaction, or unusual configuration, to remotely modify or delete public projects and user data.
Secondary reporting (WatchTowr, corroborated across multiple press outlets) names the abused mechanism as the @gl_introduced GraphQL directive and describes an attacker's practical reach as broader than the vendor advisory's own wording suggests: deleting entire repositories, forging merge records to make it look as though a fix landed when it did not, and banning or removing project maintainers — all via a single unauthenticated HTTP request. WatchTowr researcher Jake Knott stated the firm reproduced the flaw "within minutes" of disclosure using only the advisory and the patch diff, and its honeypot network caught real-world exploitation attempts within days.
The CVSS 3.1 vector — AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H — is the load-bearing technical fact for scoping this pack: Confidentiality is rated Low, Integrity and Availability are rated High. This is a data-integrity and data-destruction vulnerability at the application/repository layer, not a documented remote-code-execution or webshell primitive. No source reviewed for this pack claims OS-level RCE as part of CVE-2026-19478 itself. Any process/network/persistence hunt content in this pack (Section 8, Q2–Q6) targets a secondary, hypothetical follow-on chain — e.g. an attacker who tampers with a public project's CI/CD configuration or merges malicious code that later executes on a build runner — and is explicitly labelled as inferred, not vendor-asserted.
Defender priority: The exploitation string @gl_introduced in an inbound GraphQL request is the single most direct, lowest-false-positive signal for this CVE, but it lives in application/web logs, not default Falcon EDR sensor telemetry — the endpoint sensor does not capture inbound HTTP request bodies to a self-hosted web application. If you have not ingested GitLab, reverse-proxy, or WAF logs into Falcon Next-Gen SIEM (or another log platform), your first control is patching to 19.2.4 / 19.1.6 / 19.0.8 / 18.11.11 and restricting unauthenticated access to /api/graphql at the network edge — not a detection query.
Source Review & Web Hunter Notes
Eight sources were fetched and snapshotted verbatim to GitLab-CVE-2026-19478-Hunt-sources/. Every fact and indicator shipped in this pack traces to one of those snapshots.
| Tier | Source | Key finding | Carry forward |
|---|---|---|---|
| 1 · Gov | NVD — CVE-2026-19478 detail (fetched) | CVSS 3.1 vector/score, CWE-94, affected version ranges, publish/modify dates | Yes — primary basis for severity and scope |
| 1 · Vendor | GitLab Docs — official patch release notes (fetched) | Release date, exact CVE description, companion CVE-2026-19650, reporter credit | Yes — primary basis for patch/version facts |
| 2 · Press | The Hacker News — CVE-2026-19478 exploitation report (fetched) | WatchTowr attribution, @gl_introduced directive name, hunt guidance (web logs, /api/graphql) | Yes — primary basis for the detection angle |
| 2 · Press | CSO Online — GitLab flaw article (fetched) | Jake Knott (WatchTowr) direct quotes, honeypot exploitation timeline, mitigation checklist | Yes — corroboration + quotes |
| 2 · Press | eSecurity Planet — CVE-2026-19478 article (fetched) | Corroborates WatchTowr quotes, supply-chain-risk framing, mitigation checklist | Yes — corroboration + hardening detail |
| 3 · Researcher | Horizon3.ai — attack research page (fetched) | CWE-94 framing, confirms the vendor advisory itself does not name the directive publicly | Partial — corroboration only |
| 3 · Researcher | CyCognito — emerging threat blog (fetched) | Impact detail (source, docs, CI/CD config, IaC files), mitigation checklist | Partial — corroboration only |
| 4 · Aggregate | Search aggregation — CISA KEV status check (fetched/logged) | CISA KEV listing and remediation-due-date could not be confirmed by direct fetch (cisa.gov returned HTTP 403 to automated fetch twice) | No — gap, not carried forward as fact |
Research notes and deliberate exclusions
- The
@gl_introduceddirective name is a T2/WatchTowr-attributed technical detail, not verbatim text in the T1 GitLab advisory as fetched. GitLab's own patch-release notes describe "a GraphQL directive" without naming it. The name is corroborated consistently across three independent T2 outlets reporting WatchTowr's findings, so it is carried forward at high confidence, but the distinction is preserved rather than presented as vendor-confirmed language. - No file hashes, C2 domains, or IPv4 indicators are shipped in this pack. This CVE is a logic/data-integrity flaw exploited via crafted HTTP requests against application logic, not malware with attacker-controlled infrastructure. Section 4 and Section 10 reflect that honestly rather than manufacturing atomic IOCs to fill a table.
- CISA KEV status is an explicit gap, not a claim. Multiple secondary aggregators reference KEV activity in the same week, and one search-engine summary conflated this CVE with an unrelated "Ray" framework KEV entry — that conflation was discarded, not carried into this brief. Two direct fetch attempts against cisa.gov (the alert page and the KEV catalog page) both returned HTTP 403. Any KEV-specific claim in this pack is marked as unverified; validate directly against
cisa.gov/known-exploited-vulnerabilities-catalogbefore citing KEV status in a customer-facing document. - No RCE is asserted for CVE-2026-19478 itself. The CVSS vector (
C:L/I:H/A:H) and every fetched source describe data modification, deletion, and account-access removal — not code execution on the underlying host. Section 8's process/network/persistence queries (Q2–Q6) are explicitly framed as hunting a hypothetical secondary chain, per the task brief for this pack. - No exploit code was fetched, reproduced, or included. WatchTowr's reproduction and public PoC availability are referenced descriptively only.
Hunt Brief & Attack Chain
Hunt hypotheses (ordered by fidelity)
| # | Hypothesis | MITRE | Telemetry | Conf |
|---|---|---|---|---|
| H1 | Unauthenticated requests to /api/graphql containing the @gl_introduced directive are probing or exploiting CVE-2026-19478 | T1190 | Ingested GitLab / reverse-proxy / WAF HTTP logs | HIGH |
| H2 | A GitLab application-server process (Puma/Sidekiq/Ruby) spawns an unexpected interpreter or shell child following exploitation (secondary/hypothetical — not documented by the CVE itself) | T1059.004 (inferred) | ProcessRollup2 | MED |
| H3 | A new executable or script is written into a GitLab install/data path by an application-server process (webshell drop) (inferred) | T1505.003 (inferred) | NewExecutableWritten, ELFFileWritten | MED |
| H4 | A GitLab application-server host makes unusual outbound connections following tampering (inferred) | T1041 / T1071 (inferred) | NetworkConnectIP4 | MED |
| H5 | A GitLab application-server host resolves rare or first-seen domains (inferred) | T1071.004 (inferred) | DnsRequest | LOW |
| H6 | An attacker who tampered with a public project's CI/CD configuration establishes persistence on a CI runner host (speculative) | T1053.005 / T1543 (inferred) | ScheduledTaskRegistered, CreateService | LOW |
Attack chain
| Step | Behavior | Telemetry | Hunt angle |
|---|---|---|---|
| 1 · Recon | Attacker identifies an internet-facing self-managed GitLab instance running an unpatched version (18.2–18.11.10, 19.0–19.0.7, 19.1–19.1.5, or 19.2–19.2.3) with at least one public project | External attack-surface / version data | Playbook 1 (version check) |
| 2 · Initial access / exploitation | Unauthenticated POST to /api/graphql containing the @gl_introduced directive triggers CWE-94 code injection in GitLab's GraphQL layer | GitLab / reverse-proxy / WAF HTTP logs | Q1 |
| 3 · Impact — data manipulation | Attacker rewrites public project data or forges merge records to fake a fix landing | GitLab audit events, Git reflog / object history | Native hunt (Section 7) |
| 4 · Impact — data destruction | Attacker deletes public projects or entire repositories | GitLab audit events, backup diffs | Native hunt (Section 7) |
| 5 · Impact — account access removal | Attacker bans or removes project maintainers | GitLab audit events (membership changes) | Native hunt (Section 7) |
| 6 · Secondary (hypothetical) — local execution | If tampering extends to CI/CD config or a merged malicious commit runs on a build runner, an interpreter/shell or dropped script appears on the app or runner host | ProcessRollup2, NewExecutableWritten / ELFFileWritten | Q2, Q3 |
| 7 · Secondary (hypothetical) — C2 / exfil | Anomalous outbound network or DNS activity from the app-server host | NetworkConnectIP4, DnsRequest | Q4, Q5 |
| 8 · Secondary (hypothetical) — persistence | Scheduled task or service creation on a CI/CD runner host | ScheduledTaskRegistered, CreateService | Q6 |
Consolidated IOC Table
CVE-2026-19478 is a logic/data-integrity vulnerability, not malware with attacker-controlled infrastructure. No file hashes, C2 domains, or IPv4 addresses have been published by any source reviewed for this pack. The table below reflects that honestly — see Section 10 for the corresponding Behavioral Signatures and Named-Vulnerability quick-copy blocks.
| Type | Value | Conf | Action | Context |
|---|---|---|---|---|
| String / GraphQL directive | @gl_introduced | HIGH | hunt | Exploitation indicator per WatchTowr — hunt web/proxy/WAF logs for this literal string in requests to /api/graphql |
| Software version (vulnerable) | GitLab CE/EE 18.2–18.11.10, 19.0–19.0.7, 19.1–19.1.5, 19.2–19.2.3 | HIGH | enrich | Version-audit target for self-managed instances (Playbook 1) |
| Software version (patched) | GitLab CE/EE 19.2.4, 19.1.6, 19.0.8, 18.11.11 | HIGH | enrich | Target state for the patch — confirm and verify |
| CVE identifier | CVE-2026-19478 (companion: CVE-2026-19650) | HIGH | enrich | Reference / vulnerability-management tracking |
| File hash | (none published) | — | — | No malware payload is associated with this CVE |
| Domain / C2 | (none published) | — | — | No attacker-controlled infrastructure has been published |
| IPv4 / IPv6 | (none published) | — | — | No attacker-controlled infrastructure has been published |
Affected Surface & Telemetry Matrix
| Surface | Required telemetry | Priority | Gap risk |
|---|---|---|---|
Self-managed GitLab CE/EE app servers exposing /api/graphql unauthenticated, with public projects enabled | GitLab / reverse-proxy / WAF HTTP access logs (ingested to a SIEM) | CRITICAL | HIGH — this is the actual exploitation surface and it is NOT default Falcon EDR telemetry |
| GitLab.com and GitLab Dedicated tenants | None — vendor-patched automatically | LOW | Low — customer action not required |
| GitLab application-server host (Puma/Sidekiq/Ruby processes) | ProcessRollup2, SyntheticProcessRollup2, NewExecutableWritten, ELFFileWritten | HIGH | Medium — only detects a hypothetical secondary local-execution chain, not the CVE itself |
| GitLab application-server network egress | NetworkConnectIP4, DnsRequest | MEDIUM | Medium — high baseline noise from legitimate integrations |
| CI/CD runners executing pipelines from public/tampered projects | ScheduledTaskRegistered, CreateService, ProcessRollup2 | MEDIUM | High — speculative secondary exposure, not directly tied to the CVE by any source |
| GitLab audit / application logs (project deletion, membership changes, merge-request state) | GitLab Audit Events (native, non-Falcon) | CRITICAL | Medium — audit event streaming may require a paid GitLab tier; see Section 7 |
ATT&CK Mapping
| Tactic | Technique | Observed behavior | Query / control |
|---|---|---|---|
| Initial Access | T1190 — Exploit Public-Facing Application | Unauthenticated GraphQL directive injection against /api/graphql | Q1 · patch + edge restriction |
| Impact | T1565.001 — Data Manipulation: Stored Data Manipulation | Rewritten project data; forged merge records faking a fix landing | Native hunt (Section 7) · GitLab audit events |
| Impact | T1485 — Data Destruction | Deletion of public projects / entire repositories | Native hunt (Section 7) · backup verification |
| Impact | T1531 — Account Access Removal | Project maintainers banned or removed | Native hunt (Section 7) · GitLab audit events |
| Execution (inferred) | T1059.004 — Unix Shell | Interpreter/shell spawned by a GitLab app-server process post-tamper | Q2 |
| Persistence (inferred) | T1505.003 — Web Shell | New executable/script written into GitLab install/data path | Q3 |
| Command and Control (inferred) | T1071 / T1041 — C2 & Exfiltration | Unusual outbound connection from the app-server host | Q4 |
| Command and Control (inferred) | T1071.004 — DNS | Rare/first-seen domain resolution from the app-server host | Q5 |
| Persistence (inferred) | T1053.005 / T1543 — Scheduled Task / Create or Modify System Process | Task or service creation on a CI/CD runner host | Q6 |
| Impact (inferred, downstream) | T1195.002 — Compromise Software Supply Chain | Tampered CI/CD config or forged merge history reaching a build/deploy pipeline | Hardening: protected branches, required approvals (Section 12) |
Native Audit-Log Hunts
The actual exploitation and its highest-value evidence live in GitLab's own logs, not in EDR telemetry. Run these in parallel with, and ahead of, the CQL hunts in Section 8.
- GitLab Rails/API access log (
/var/log/gitlab/gitlab-rails/api_json.logon an omnibus install, or your ingested equivalent) — grep or SIEM-search for@gl_introducedin requests to/api/graphql, especially from source IPs with no prior authenticated session. - GitLab Audit Events (Admin Area > Monitoring > Audit Events, or Audit Event Streaming on Premium/Ultimate) — filter for project deletion, project visibility/data changes, merge-request state changes, and member-removal events around and after the disclosure window (2026-08-17 onward), especially any attributed to an unexpected or anonymous actor.
- Reverse-proxy / nginx access logs — POST requests to
/api/graphqlwith no corresponding authenticated session cookie or bearer token. - WAF / CDN logs — request-body inspection for
@gl_introduced, if body logging is enabled. - Backup integrity checks — confirm your most recent known-good backup predates any suspected tampering window before relying on it for recovery.
- Git reflog / object store review — orphaned commits, unexpected force-pushes, or merge commits inconsistent with your normal review workflow are consistent with forged merge records.
- Software/version inventory — reconcile every self-managed GitLab instance against the patched version list (19.2.4 / 19.1.6 / 19.0.8 / 18.11.11); see Playbook 1 for a scripted check.
CrowdStrike LogScale CQL Hunt Queries
Looks for: the literal @gl_introduced GraphQL directive string in inbound HTTP requests — the most direct signal for this CVE per WatchTowr's technical reporting. Requires non-default telemetry: GitLab / reverse-proxy / WAF logs ingested into Falcon Next-Gen SIEM as a custom log source. The endpoint sensor does not capture inbound HTTP request bodies to a self-hosted web application, so this query returns nothing on a tenant relying on EDR telemetry alone. FP: your own post-patch validation traffic or an authorized vulnerability scan will also match.
// HUNT: GraphQL directive abuse pattern (@gl_introduced) against /api/graphql // MITRE: T1190 // CONF: high | FP: low | COST: medium // REQUIRES: GitLab / reverse-proxy / WAF HTTP logs ingested into Falcon NG-SIEM as a custom log source -- NOT default Falcon EDR sensor telemetry // FALSE POSITIVES: your own patch-validation test traffic; an authorized vulnerability scanner probing the same string // TUNING: exclude known scanner source IPs and your own post-patch validation test requests once identified // FIELD NOTE: field names below (host, src_ip, http_user_agent, url, method, status, raw) are generic NG-SIEM / third-party log conventions -- validate the exact field names against YOUR tenant's log-source parser before relying on this query "@gl_introduced" | table([@timestamp, host, src_ip, http_user_agent, url, method, status, raw])
Looks for: a shell or scripting interpreter spawned by a GitLab application-server process. This hunts a hypothetical secondary chain, not CVE-2026-19478 itself — the CVE is a data-integrity flaw, not documented RCE. FP: GitLab's own Sidekiq/Puma workers legitimately shell out for git hooks, housekeeping tasks, and background jobs.
// HUNT: Interpreter or shell spawned by a GitLab application-server process // MITRE: T1190, T1059.004 (inferred secondary chain -- not asserted by CVE-2026-19478 itself) // CONF: medium | FP: medium | COST: low // REQUIRES: ProcessRollup2, SyntheticProcessRollup2 on the GitLab application-server host(s) // FALSE POSITIVES: GitLab's own Sidekiq/Puma workers legitimately shell out for git hooks, housekeeping, and background jobs // TUNING: baseline your own git/gitlab-shell/gitlab-workhorse child processes first and exclude them by CommandLine before promoting #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | ParentBaseFileName=/^(ruby|puma|unicorn|sidekiq|bundle|gitlab-rails|gitlab-workhorse)(\.exe)?$/i | FileName=/^(bash|dash|zsh|python[0-9.]*|perl|nc|ncat|socat)(\.exe)?$/i | table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine])
Looks for: a new executable or script written under GitLab's install/data paths by a GitLab application-server process. Inferred secondary chain — not documented by any source reviewed for this pack. FP: gem/asset compilation and GitLab's own upgrade process legitimately write executables under /opt/gitlab.
// HUNT: New executable or script written into GitLab install/data paths by an app-server process // MITRE: T1505.003 (inferred secondary chain -- not asserted by CVE-2026-19478 itself) // CONF: medium | FP: medium | COST: low // REQUIRES: NewExecutableWritten, ELFFileWritten on the GitLab application-server host(s) // FALSE POSITIVES: gem/asset compilation and GitLab's own upgrade process legitimately write executables under /opt/gitlab // TUNING: exclude writes occurring inside a known maintenance/upgrade window (correlate against your change calendar) #event_simpleName=/^(NewExecutableWritten|ELFFileWritten)$/ | ContextBaseFileName=/^(ruby|puma|unicorn|sidekiq|bundle|gitlab-rails|gitlab-workhorse|nginx)(\.exe)?$/i | TargetFileName=/(\/opt\/gitlab\/|\/var\/opt\/gitlab\/|\/home\/git\/)/i | table([@timestamp, ComputerName, ContextBaseFileName, TargetFileName])
Looks for: outbound connections from GitLab app-server processes to non-RFC1918 addresses, grouped for triage. Inferred secondary chain. FP: license checks, container-registry pulls, webhook/integration calls, git remote mirroring, and outbound email all legitimately leave the app server — expect a noisy baseline until tuned.
// HUNT: Unusual outbound connection from a GitLab application-server process // MITRE: T1041, T1071 (inferred secondary chain) // CONF: medium | FP: high | COST: medium // REQUIRES: NetworkConnectIP4 on the GitLab application-server host(s) // FALSE POSITIVES: license checks, container-registry pulls, webhook/integration calls, git remote mirroring, outbound email // TUNING: exclude your known SaaS integrations (webhooks, registry mirrors, license server) by RemoteAddressIP4/RemotePort before alerting #event_simpleName=/^NetworkConnectIP4$/ | ContextBaseFileName=/^(ruby|puma|unicorn|sidekiq|bundle|gitlab-rails|gitlab-workhorse)(\.exe)?$/i | !cidr(RemoteAddressIP4, subnet=["10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"]) | groupBy([ComputerName, ContextBaseFileName, RemoteAddressIP4, RemotePort], function=count()) | sort(_count, order=desc)
Looks for: low-frequency domain resolutions from GitLab app-server processes — a weak beaconing signal, not a confirmatory one. Inferred secondary chain. FP: package-manager mirrors, container-registry pulls, and one-off admin troubleshooting all produce low-count domains.
// HUNT: Rare or first-seen DNS resolution from a GitLab application-server process // MITRE: T1071.004 (inferred secondary chain) // CONF: low | FP: high | COST: medium // REQUIRES: DnsRequest on the GitLab application-server host(s) // FALSE POSITIVES: package-manager mirrors, container-registry pulls, one-off admin troubleshooting // TUNING: raise the _count threshold and add an allowlist for your package/registry mirrors before treating hits as actionable #event_simpleName=/^DnsRequest$/ | ContextBaseFileName=/^(ruby|puma|unicorn|sidekiq|bundle|gitlab-rails|gitlab-workhorse)(\.exe)?$/i | groupBy([ComputerName, DomainName], function=count()) | test(_count <= 3) | sort(_count, order=asc)
Looks for: new scheduled tasks or services registered on hosts named like CI/CD runners — a highly speculative persistence hunt for an attacker who tampered with a public project's pipeline config. Not documented as part of CVE-2026-19478 by any source reviewed. FP: legitimate CI/CD tooling and configuration-management agents register tasks/services constantly on runner hosts.
// HUNT: Scheduled task or service creation on a CI/CD runner host (possible persistence via a forged/tampered pipeline) // MITRE: T1053.005, T1543 (inferred, speculative -- not documented as part of CVE-2026-19478) // CONF: low | FP: high | COST: low // REQUIRES: ScheduledTaskRegistered, CreateService // FALSE POSITIVES: legitimate CI/CD tooling and configuration-management agents register tasks/services constantly on runner hosts // TUNING: scope ComputerName to your actual runner fleet naming convention and exclude your CM tool's service account before hunting further #event_simpleName=/^(ScheduledTaskRegistered|CreateService)$/ | ComputerName=/(runner|ci|build)/i | table([@timestamp, ComputerName, UserName, TaskName, ServiceName, ServiceImagePath])
CrowdStrike Custom IOA Recommendations
No query in this pack is a clean Block-mode Custom IOA candidate. Q1 is the highest-confidence detection but it is a log query (Falcon Next-Gen SIEM Scheduled Search), not sensor-based behavior — Custom IOAs are endpoint-sensor rules and do not apply to ingested HTTP logs. Q2 and Q3 have workable confidence but medium FP from legitimate GitLab internals; Q4–Q6 carry high FP or low confidence and are investigate-only.
| Query | Promotion path | Action | Notes |
|---|---|---|---|
| Q1 | Falcon Next-Gen SIEM Scheduled Search (not a Custom IOA) | Alert (detect) | Primary alerting mechanism for this pack — requires GitLab/proxy/WAF log ingestion first |
| Q2 | Custom IOA — Monitor mode only | Monitor | Baseline for 1–2 weeks against your own git-internal child-process patterns before considering Alert mode |
| Q3 | Custom IOA — Monitor mode only | Monitor | Correlate against your GitLab upgrade/maintenance calendar before promoting |
| Q4, Q5, Q6 | Investigate-only | Hunt | High FP / low confidence — not promotion candidates at this time |
Alert email package — Q1 (primary detection)
Scheduled Search name: DETECT — GitLab GraphQL @gl_introduced Directive Abuse (CVE-2026-19478)
Description: Fires when the literal GraphQL directive string @gl_introduced appears in ingested GitLab, reverse-proxy, or WAF HTTP logs — the primary indicator of a probe or active exploitation attempt against the unauthenticated CVE-2026-19478 code-injection flaw.
Alert email subject: [CRITICAL] Detection: GitLab CVE-2026-19478 — GraphQL directive abuse (@gl_introduced) observed
Alert email body:
------------------------------------------------------------
A request containing the GraphQL directive string "@gl_introduced" was
observed against /api/graphql on {{ host }} from source {{ src_ip }} at
{{ @timestamp }}.
This string is the primary exploitation indicator for CVE-2026-19478, a
CVSS 9.4 unauthenticated code-injection flaw in GitLab's GraphQL layer
(CWE-94). Successful exploitation can modify or delete public projects,
forge merge records, and remove project maintainers WITHOUT any
credentials or user interaction.
IMMEDIATE ACTIONS:
1. Confirm the target GitLab instance's patch level. If it is NOT on
19.2.4 / 19.1.6 / 19.0.8 / 18.11.11 (or later), treat this as an
active incident, not a hunt lead.
2. Restrict unauthenticated access to /api/graphql at the reverse
proxy / WAF immediately (Playbook 2) if patching cannot happen
within the hour.
3. Pull GitLab Audit Events for the affected instance covering the
alert window +/- 24h; look for project deletion, data-visibility
changes, merge-request state changes, and maintainer removal.
4. Do NOT assume "no visible damage" means no compromise -- integrity
tampering (forged merge records) is designed to look clean.
ESCALATION: Platform/Infra on-call -> Security IR lead if step 1 confirms
an unpatched, internet-facing instance with a real hit.
------------------------------------------------------------
Triage checklist
- Confirm the alerting request actually targeted
/api/graphqland not an unrelated field that happens to contain the string. - Check the target GitLab instance's version against the patched list (Playbook 1).
- Identify the source IP — is it a known vulnerability scanner or your own patch-validation tooling? If so, close as benign and add to the Q1 TUNING exclusion.
- If the source is unknown/external, pull GitLab Audit Events for the same window and instance.
- Check for project deletions, visibility changes, merge-request state changes, or maintainer removals in that window.
- If any of the above are found, escalate to Security IR — this is a confirmed exploitation attempt with observable impact, not a probe.
- Preserve the raw log lines and any GitLab audit-event export before further remediation (evidence for Section 14, Phase 1).
- Regardless of confirmed impact, patch and restrict
/api/graphqlimmediately if not already done.
Pivot queries
Host-scoped: re-run Q2 and Q3 scoped to the same ComputerName as the alerting GitLab instance, over the 24 hours following the Q1 hit, to check for the hypothetical secondary local-execution chain.
Environment-wide: run Q1 across all ingested GitLab instances for the last 30 days (extend the console time picker) to check for earlier, previously undetected probing that predates this alert.
IOC blocklist entries: not applicable — this CVE has no published atomic indicators to block (see Section 4).
Hunt package — Q2 (investigate-only, lighter)
Q2's medium FP means it should not alert unattended. Run it manually against any host that also produced a Q1 hit, or periodically as a hygiene sweep. Pivot: correlate any hit against Q3 (same host/window) — a webshell-style file write alongside an unexpected shell child materially raises confidence over either signal alone.
Machine-Readable IOC Appendix
# CVE-2026-19478 is a logic/data-integrity flaw in GitLab's GraphQL layer, not # malware with attacker-controlled infrastructure. No file hashes, C2 domains, or # IPv4 addresses have been published by any source reviewed for this pack -- # shipping fabricated atomic IOCs here would fail this pack's own provenance gate. # # Track the vulnerability instead by: # CVE ID: CVE-2026-19478 (companion: CVE-2026-19650) # CVSS 3.1: 9.4 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H) # CWE: CWE-94 Improper Control of Generation of Code (Code Injection) # Exploitation string: @gl_introduced (GraphQL directive -- see Behavioral Signatures block) # # If your own incident response later recovers attacker infrastructure (source # IPs, follow-on payload hashes, etc.) from a CONFIRMED compromise, add rows here # in standard type,value,action,severity,expiration,description,tags form and # re-run check_ioc_provenance.py against a newly saved source snapshot.
# CVE-2026-19478 behavioral signatures (no atomic IOC form)
# Exploitation indicator
GraphQL directive string in request body/URI: @gl_introduced
Target endpoint: POST /api/graphql
Requires: NO authentication, NO user interaction (per GitLab advisory + NVD)
# Impact behaviors to hunt in GitLab Audit Events (Section 7)
Public project data modified or deleted without a corresponding authenticated
admin/maintainer session
Merge request marked merged/closed with no matching reviewer action
("forged merge record" pattern per WatchTowr)
Project maintainer removed or banned with no corresponding admin action
# Secondary/hypothetical chain (inferred, not vendor-asserted)
GitLab app-server process (ruby/puma/unicorn/sidekiq/gitlab-rails/gitlab-workhorse)
spawning bash/dash/zsh/python/perl/nc/ncat/socat
New executable or script written under /opt/gitlab/, /var/opt/gitlab/, or /home/git/
by a GitLab app-server process
Primary CVE: CVE-2026-19478 -- CVSS 3.1 9.4 Critical -- CWE-94
Companion CVE: CVE-2026-19650 -- CVSS 3.1 7.1 High -- CSRF in GraphQL multiplex
query handler (requires user interaction)
Vulnerable: GitLab CE/EE 18.2 < 18.11.11, 19.0 < 19.0.8,
19.1 < 19.1.6, 19.2 < 19.2.4
Patched: GitLab CE/EE 19.2.4, 19.1.6, 19.0.8, 18.11.11
(released 2026-08-17, out-of-band critical patch release)
Not affected: GitLab.com, GitLab Dedicated (auto-patched)
Reporter: hiimguardian (HackerOne bug bounty)
Researcher: WatchTowr (Jake Knott) -- reproduction + honeypot exploitation
Exploited by: Unknown/unattributed opportunistic scanning observed by WatchTowr
honeypot network within days of disclosure
# Version and exposure audit -- run against every self-managed GitLab instance [ ] Confirm version is 19.2.4 / 19.1.6 / 19.0.8 / 18.11.11 or later (Playbook 1) [ ] If not yet patched: restrict unauthenticated access to /api/graphql NOW (Playbook 2) [ ] Inventory which projects on this instance are set to Public visibility (Playbook 3) [ ] Confirm GitLab Audit Events (or equivalent log export) are being retained/ingested [ ] Verify last-known-good backup predates 2026-08-17 (pre-disclosure) for comparison [ ] Confirm whether this instance is GitLab.com / Dedicated (no action) or self-managed (action required)
Import note: this pack ships no rows for Falcon IOC Management by design (see the CSV block above). The importable content here is procedural (patch/version state and audit checklist), not atomic threat infrastructure.
Detection Validation Gates
Gate 1 — Telemetry ready
- Q1 requires log ingestion that most tenants do not have by default. Before treating a zero-hit Q1 result as "clean," confirm GitLab/reverse-proxy/WAF logs are actually flowing into Falcon NG-SIEM (or wherever Q1 runs) — a zero-hit result against an empty log source is not a clean bill of health, it's a blind spot.
- Confirm
ProcessRollup2/SyntheticProcessRollup2andNewExecutableWritten/ELFFileWrittenare populated for your GitLab application-server hosts specifically (not just Windows fleet defaults — these are typically Linux hosts). - Confirm
ScheduledTaskRegistered/CreateServicecoverage on your CI/CD runner fleet before relying on Q6.
Gate 2 — Benign baseline
- Run Q2, Q3, Q4, and Q5 over 7 days with no exclusions and record hit counts before promoting any to Monitor mode. Expect Q4 and Q5 to be noisy — that noise is the baseline you tune against, not a false alarm to dismiss.
- Q1 should return zero in a clean environment absent scanning activity; a non-zero baseline needs individual triage (Section 9 triage checklist), not blanket suppression.
Gate 3 — Positive tests
- In a non-production log source, submit a benign test event containing the literal string
@gl_introducedand confirm Q1 fires end-to-end (ingestion → search → alert). - On a lab GitLab host, manually spawn a shell from a process named like one of the Q2 parent patterns (e.g. a test script invoked as
ruby) and confirm Q2 fires; clean up afterward.
Gate 4 — Promotion criteria
- Q1 promotes to an active Scheduled Search alert only after Gate 1 (telemetry confirmed present) and Gate 3 (positive test fired) both pass.
- Q2/Q3 promote from Monitor to Alert only after 7+ days at a stable, explained baseline (Gate 2) with your GitLab-internal exclusions applied.
- Do not promote Q4, Q5, or Q6 to alerting in this release — they remain investigate-only pending environment-specific tuning.
Hardening — Tiered
- Patch every self-managed GitLab CE/EE instance to 19.2.4 / 19.1.6 / 19.0.8 / 18.11.11 (or later) (MITRE M1051 · vendor advisory). This is a critical, out-of-band release with confirmed in-the-wild exploitation — do not wait for the next normal patch window. Verify with Playbook 1.
- Restrict unauthenticated access to
/api/graphqlat the reverse proxy or WAF for any instance that cannot be patched within the hour (MITRE M1030 · M1037 — Network Segmentation / Filter Network Traffic). Deploy with Playbook 2. - Audit and reduce public project visibility where it is not operationally required (GitLab visibility settings; no formal CIS benchmark covers this GitLab-specific control — ⚠ best-practice, no formal benchmark). Run the read-only inventory in Playbook 3.
- Ingest GitLab audit events and/or reverse-proxy access logs into your SIEM (MITRE M1047 — Audit; no formal CIS benchmark for this GitLab-specific control — ⚠ best-practice, no formal benchmark). This is a hard prerequisite for Q1 in Section 8 and for the native hunts in Section 7 — without it, this CVE class is effectively invisible to your detection stack.
- Deploy a temporary WAF virtual-patch rule blocking/alerting on
@gl_introducedin requests to/api/graphqlas a compensating control during the patch rollout window (MITRE M1037). See Playbook 4. Retire once all instances are confirmed patched. - Review and harden your backup cadence and immutability for GitLab data (MITRE M1053 — Data Backup). This vulnerability's blast radius explicitly includes destructive deletion; recovery depends on backups the attacker cannot also tamper with.
- Network-segment CI/CD runners and GitLab admin interfaces away from general-purpose network access (MITRE M1030). Limits the blast radius of the speculative CI/runner persistence chain in Q6.
- Establish a formal patch-cadence SLA for GitLab self-managed critical/high advisories — subscribe to GitLab's security-release channel and commit to a defined response window (e.g. 72 hours for Critical). ⚠ best-practice, no formal benchmark
- Enforce protected branches, required merge approvals, and (where supported) commit signing on public/high-value projects (MITRE M1047 · GitLab protected-branch settings). Directly reduces the blast radius of the forged-merge-record and CI/CD supply-chain risk described in Section 6 (T1195.002).
Deployable Playbooks
1 — Version verification (shell)
# Check current GitLab version on an omnibus install sudo gitlab-rake gitlab:env:info 2>/dev/null | grep -A2 "GitLab information" # Alternative -- read the version manifest directly cat /opt/gitlab/version-manifest.txt | grep -i gitlab-ce # PASS if version is 19.2.4 / 19.1.6 / 19.0.8 / 18.11.11 or later. # FAIL (patch immediately) if version is in any of: # 18.2 - 18.11.10, 19.0 - 19.0.7, 19.1 - 19.1.5, 19.2 - 19.2.3
Prerequisites: SSH/admin access to the GitLab omnibus host. Reboot required: no (read-only check). Rollback: none required — this is a read-only verification step.
2 — Restrict /api/graphql at the reverse proxy (nginx, temporary compensating control)
# /etc/nginx/conf.d/gitlab-graphql-restrict.conf
# Place inside (or alongside) the existing GitLab server block, then reload.
location /api/graphql {
# Restrict to known internal/CI ranges only -- adjust to your environment.
allow 10.0.0.0/8;
allow 172.16.0.0/12;
deny all;
}
sudo nginx -t && sudo nginx -s reload
Prerequisites: nginx (or equivalent reverse proxy) terminating in front of GitLab; admin access to edit its config. Reboot required: no. Rollback: remove the location /api/graphql block from the config file, then run sudo nginx -t && sudo nginx -s reload again.
3 — Public-project visibility audit (GitLab Rails console, read-only)
# Run from: sudo gitlab-rails console Project.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).pluck(:id, :path_with_namespace)
Prerequisites: admin access to gitlab-rails console on the omnibus host. Reboot required: no. Rollback: none required — this is a read-only query; no state is changed. Changing a project's visibility afterward is a separate, deliberate admin action taken outside this playbook.
4 — Temporary WAF virtual-patch rule for @gl_introduced (ModSecurity example)
# /etc/modsecurity.d/gitlab-cve-2026-19478-virtual-patch.conf
SecRule REQUEST_URI "@streq /api/graphql" \
"chain,id:1000019478,phase:2,deny,log,msg:'Blocked possible CVE-2026-19478 GraphQL directive abuse'"
SecRule REQUEST_BODY "@contains @gl_introduced" "t:none"
sudo apachectl configtest && sudo systemctl reload apache2 # or, for an nginx+ModSecurity install: sudo nginx -t && sudo nginx -s reload
Prerequisites: ModSecurity (or an equivalent WAF engine) in the request path in front of GitLab, with request-body inspection enabled. Reboot required: no (WAF config reload only). Rollback: remove or disable rule id 1000019478 and reload the WAF engine. This is explicitly a temporary compensating control — retire it once every instance is confirmed on a patched version (Playbook 1).
Containment Runbook
| Phase | Actions | Owner | Evidence to capture |
|---|---|---|---|
| 1 · Isolate 0–1 h | Restrict or temporarily disable unauthenticated access to /api/graphql at the edge (Playbook 2/4) on the affected instance while investigating. Do not take the instance fully offline unless data destruction is confirmed in progress. | Infra/Platform on-call | Firewall/WAF change ticket, timestamp, Q1 alert detail |
| 2 · Confirm 0–2 h | Verify current patch level (Playbook 1). Pull GitLab Audit Events for the suspected window; confirm whether project deletion, data-visibility changes, merge-request state changes, or maintainer removal actually occurred. | Security IR | Version-check output, exported audit events, affected project list |
| 3 · Scope 1–4 h | Enumerate every self-managed GitLab instance in the environment; re-run Playbook 1 and the Section 10 audit checklist against each. Run Q1 environment-wide over the last 30 days. | Threat hunting | Instance inventory, version audit results, Q1 hit list |
| 4 · Eradicate 2–24 h | Patch every affected instance to 19.2.4 / 19.1.6 / 19.0.8 / 18.11.11 (or later). Rotate any CI/CD variables, deploy tokens, or credentials that were reachable from a tampered project. | Platform eng. | Patch confirmation, credential-rotation log |
| 5 · Recover 1–3 d | Restore any deleted repositories from a backup confirmed to predate the tampering window (Playbook 1's version check + Section 7's backup-integrity note). Re-verify merge history for forged/backdated merge records; reinstate wrongfully removed maintainers. | Platform eng. + repo owners | Restore log, merge-history diff, maintainer-reinstatement record |
| 6 · Notify 1–5 d | If public-repo data integrity was affected, notify downstream consumers (contributors, CI/CD consumers of the affected repo, package registries mirroring it) so they can re-verify what they pulled during the tampering window. | IR lead + repo owners | Notification record, list of downstream consumers contacted |
| 7 · Harden 1–4 w | Execute the Section 12 Immediate and Near-term tiers across the full GitLab fleet, not just the affected instance. Feed lessons learned into your patch-cadence SLA. | Security eng. | Change records, log-ingestion confirmation, SLA document update |
Detection Coverage Map
| Technique | Behavior | CQL | IOA | Coverage |
|---|---|---|---|---|
| T1190 | Unauthenticated GraphQL directive injection against /api/graphql | Q1 | — (SIEM alert, not IOA) | PARTIAL — correct query, but requires log ingestion most tenants lack by default |
| T1565.001 | Stored data manipulation / forged merge records | — | — | GAP — no Falcon telemetry; covered only by GitLab Audit Events (Section 7) |
| T1485 | Public project / repository deletion | — | — | GAP — no Falcon telemetry; covered only by GitLab Audit Events + backups (Section 7) |
| T1531 | Maintainer banned/removed | — | — | GAP — no Falcon telemetry; covered only by GitLab Audit Events (Section 7) |
| T1059.004 (inferred) | Interpreter/shell spawned by GitLab app-server process | Q2 | Monitor-only | PARTIAL — medium FP, hypothetical secondary chain |
| T1505.003 (inferred) | Webshell-style file write into GitLab paths | Q3 | Monitor-only | PARTIAL — medium FP, hypothetical secondary chain |
| T1041 / T1071 (inferred) | Unusual outbound connection from app server | Q4 | — | PARTIAL — high FP, needs environment-specific tuning |
| T1071.004 (inferred) | Rare DNS resolution from app server | Q5 | — | PARTIAL — weak signal, high FP |
| T1053.005 / T1543 (inferred, speculative) | Persistence on CI/CD runner host | Q6 | — | GAP — speculative, high FP, not tied to the CVE by any source |
| T1195.002 (inferred, downstream) | Supply-chain reach via tampered CI/CD config | — | — | GAP — addressed only by hardening controls (Section 12), not detection |
This pack's most important honesty point is here: the CVE's actual documented impact (T1565.001 / T1485 / T1531) has zero Falcon EDR coverage, because it is an application-layer data-integrity attack, not an endpoint compromise. Detection depends entirely on GitLab's own audit logging (Section 7) and, for the @gl_introduced string specifically, on ingesting web/proxy/WAF logs into a SIEM (Q1). The endpoint-telemetry queries (Q2–Q6) hunt a hypothetical secondary chain and should not be mistaken for coverage of the CVE itself. Clear Gate 1 in Section 11 — especially the log-ingestion prerequisite — before treating a zero-hit Q1 result as a clean bill of health.
Hunt Summary Ticket
TITLE: Hunt -- CVE-2026-19478 GitLab unauthenticated GraphQL code injection
(@gl_introduced directive abuse)
SEVERITY: Critical -- CVSS 9.4, unauthenticated, exploited in the wild within
days of disclosure. Self-managed GitLab CE/EE only; GitLab.com and
Dedicated are already patched.
SCOPE: Every self-managed GitLab CE/EE instance not yet on 19.2.4 / 19.1.6 /
19.0.8 / 18.11.11. Priority: internet-facing instances with public
projects enabled.
HYPOTHESIS: An unauthenticated attacker sent a crafted GraphQL request containing
the @gl_introduced directive to /api/graphql, triggering a CWE-94 code
injection that allowed rewriting or deleting public project data,
forging merge records, and/or removing maintainers -- all without any
credentials or user interaction. This is a DATA-INTEGRITY /
DATA-DESTRUCTION vulnerability, not documented RCE.
QUERIES: Q1 @gl_introduced directive string in ingested GitLab/proxy/WAF logs
<-- MOST DIRECT SIGNAL, but requires non-default log ingestion
Q2 Interpreter/shell spawned by a GitLab app-server process (secondary)
Q3 New executable/script written into GitLab paths (secondary)
Q4 Unusual outbound connection from a GitLab app-server process (secondary)
Q5 Rare/first-seen DNS resolution from a GitLab app-server process (secondary)
Q6 Scheduled task/service creation on a CI/CD runner host (speculative)
DO FIRST: 1. Run Playbook 1 (version check) against every self-managed GitLab
instance. Patch anything not already on 19.2.4/19.1.6/19.0.8/18.11.11.
2. If patching cannot happen within the hour, restrict /api/graphql
at the edge (Playbook 2) or deploy the WAF virtual-patch (Playbook 4).
3. Confirm whether GitLab Audit Events / access logs are being
retained or ingested (Section 11, Gate 1) -- if not, Q1 is blind
and Section 7's native hunts are your only detection path.
FINDINGS: [ ] Instances confirmed patched .......................... ____
[ ] Instances confirmed vulnerable (action required) ...... ____
[ ] Q1 hits (log-based) ................................... ____
[ ] GitLab Audit Events showing unexplained deletion/
data-manipulation/maintainer-removal ................. ____
[ ] Public projects inventoried (Playbook 3) .............. ____
GAPS: The CVE's documented impact (data manipulation, deletion, account
access removal) has NO Falcon EDR telemetry coverage -- it is visible
only in GitLab's own audit/access logs. Q2-Q6 hunt a hypothetical
secondary local-execution/persistence chain that is NOT asserted by
any source reviewed for this pack. CISA KEV status could not be
independently confirmed (cisa.gov blocked automated fetch).
ACTIONS: [ ] Patch every self-managed instance (playbook 1)
[ ] Restrict /api/graphql at the edge where unpatched (playbook 2)
[ ] Audit public project visibility (playbook 3)
[ ] Deploy temporary WAF virtual-patch if needed (playbook 4)
[ ] Ingest GitLab audit/access logs into the SIEM (Section 12, Near term)
[ ] Verify backup integrity predates 2026-08-17
OWNER: ____________________
VERSION: v0.1 (2026-08-25)
Changelog
References
| Tier | Source | Used for | Published | Accessed |
|---|---|---|---|---|
| 1 | NVD — CVE-2026-19478 | CVSS vector/score, CWE-94, affected version ranges | 2026-08-17 | 2026-08-25 |
| 1 | GitLab Docs — Critical Patch Release 19.2.4 / 19.1.6 / 19.0.8 / 18.11.11 | Official CVE description, companion CVE-2026-19650, reporter credit | 2026-08-17 | 2026-08-25 |
| 2 | The Hacker News — GitLab CVE-2026-19478 Comes Under Active Exploitation | WatchTowr attribution, @gl_introduced directive name, hunt guidance | 2026-08-21 | 2026-08-25 |
| 2 | CSO Online — Critical GitLab flaw allows attackers to delete and modify public repos | WatchTowr / Jake Knott direct quotes, exploitation timeline | ~2026-08-18/21 | 2026-08-25 |
| 2 | eSecurity Planet — GitLab Patches Critical CVE-2026-19478 GraphQL Vulnerability | Corroborating quotes, supply-chain framing, mitigation checklist | ~2026-08-18/21 | 2026-08-25 |
| 3 | Horizon3.ai — CVE-2026-19478 attack research | CWE-94 framing, confirms directive name absent from vendor advisory text | ~2026-08-18/21 | 2026-08-25 |
| 3 | CyCognito — Emerging Threat: CVE-2026-19478 | Impact detail (source, docs, CI/CD config, IaC files), mitigation checklist | ~2026-08-18/21 | 2026-08-25 |
| Ref | MITRE ATT&CK | Technique and mitigation mapping | — | 2026-08-25 |
Provenance: the fetched text of all eight sources is saved alongside this pack in GitLab-CVE-2026-19478-Hunt-sources/. This pack ships no atomic file/network IOCs, so the provenance gate (check_ioc_provenance.py) has nothing to trace and passes vacuously — every factual claim in this pack is nonetheless attributable to one of the eight numbered sources above.
Unresolved gap: CISA Known Exploited Vulnerabilities (KEV) catalog status for CVE-2026-19478 could not be independently confirmed — cisa.gov returned HTTP 403 to automated fetch on both the alert page and the KEV catalog page. Validate directly at cisa.gov/known-exploited-vulnerabilities-catalog before making a KEV-status claim in a customer-facing report.