Here is the uncomfortable part. Every engineer who touches a relational database has been trained to parameterize, a discipline that exists because SQL injection taught the industry a hard lesson. But that discipline is attached in most people's heads to the word SQL, not to the concept of an interpreter that mixes code and data. Move to a graph database with its own query language, and the muscle memory does not fire. The value gets pasted into the query text with a format string, nobody flags it in review, and the same 1998-vintage bug ships in 2026. Dgraph's three flaws show that the injection family does not care what the interpreter is called. Verifiable security.
Three flaws, one root cause
All three bugs live in dgraph-io/dgraph, the open-source graph database. They differ in which request field the attacker controls and in what the query is trying to do, but the mechanism is identical: an untrusted value reaches DQL as text, and a well-placed character lets it stop being a value and start being query syntax.
The login oracle (CVE-2026-44840)
The login path builds a checkpwd() DQL query with fmt.Sprintf, dropping the user-supplied password into the query string without escaping. Because the password lands inside a DQL string literal, a password value that contains a double-quote character closes that literal early and lets the rest of the value append attacker-controlled DQL. This one is confidentiality-only, an oracle: the injected DQL changes the shape of the response, so an attacker can ask yes-or-no questions of the database through the login endpoint. That is why it is scored 7.5 High rather than Critical, and why the fix landed in 25.3.4 (and the 24.1.9 maintenance line). Its advisory is GHSA-q2m9-6jp9-c6mc.
The upsert condition (CVE-2026-41327)
Dgraph upserts let a mutation carry a cond field, a conditional expression evaluated as part of the operation. That value is concatenated into the DQL body through a string builder, so a crafted cond becomes query syntax rather than a condition. Because Dgraph ships with access control (ACL) disabled by default, the mutate endpoint answers without credentials, which means a single unauthenticated POST /mutate?commitNow=true can smuggle a read of the whole graph into what looks like a write. Pre-auth, full-database exfiltration, CVSS 9.1 Critical. Fixed in 25.3.3.
The language tag (CVE-2026-41328)
The third takes an even more obscure field. When Dgraph ingests RDF-style triples, an NQuad can carry a Lang language tag on a predicate. The internal addQueryIfUnique helper builds a DQL query with fmt.Sprintf using that Lang value unvalidated, so a crafted key of the form <predicate>@<payload> injects a named query block of the attacker's choosing. Same outcome as the upsert path: unauthenticated, whole-graph read, CVSS 9.1 Critical, fixed in 25.3.3. Its advisory is GHSA-x92x-px7w-4gx4.
The double-quote breakout is illustrative of the class, not a working payload against any host. The point is structural: a value becomes syntax.
Why a non-SQL query language is the blind spot
The reason this class keeps landing is not that the fix is hard. Parameterized queries are a solved problem that every mature database driver offers. The reason is attention: static analysis rules, code-review checklists, and developer instinct are all tuned to a small set of well-known interpreters, the shell, SQL, HTML, LDAP. A vendor-specific query language sits outside that set, so the same fmt.Sprintf that would trigger alarms around a SQL statement passes review without comment when the target is DQL.
Be precise about the severity, because that precision is the brand. The pre-auth cluster is near the worst case: no credentials, and a single request reading the entire dataset, on a database that by default does not ask who is calling. That default matters: ACL being off is what turns two parsing bugs into unauthenticated exfiltration rather than a privilege-escalation footnote. The login oracle is a real but narrower problem: it leaks through a side channel rather than dumping data outright, which is why it is High and not Critical. Getting that distinction right, 9.1 for the cluster and 7.5 for the oracle, is the difference between an accurate advisory and a scary one.
The injection family does not care what the interpreter is called. Move to a query language nobody parameterizes, and a 1998 bug ships in 2026.
How to recognize your exposure
You can assess this without sending a single hostile payload, because the exposure is decided by two readable facts and one config flag.
- Version. Fingerprint the Dgraph Alpha node through its build and
/healthsurface. A build below 25.3.3 carries the pre-auth cluster; below 25.3.4 (or 24.1.9 on the maintenance line) still carries the login oracle. - ACL state. Confirm whether access control is enabled. With ACL off, the mutate and query surfaces answer unauthenticated, which is what turns the two parsing bugs into remote data theft. ACL on removes the pre-auth reachability even before patching.
- Exposure. Determine which Alpha HTTP and GraphQL endpoints, especially
/mutate, answer from outside your trust boundary. An internal-only cluster carries a fraction of the risk of an internet-facing one.
Close DQL injection on a Dgraph deployment
- Upgrade the Alpha nodes. Move to 25.3.4 or later to clear all three, or 24.1.9 on the maintenance line. 25.3.3 clears the pre-auth cluster but not the login oracle.
- Enable ACL. Turn on access control so mutate and query stop answering unauthenticated. This is the single change that most reduces blast radius.
- Put mutate and GraphQL behind auth. Keep the Alpha data endpoints off the public internet and behind an authenticating proxy where the deployment allows it.
- Grep your own code for query-string formatting. The same pattern lives in application code that builds DQL. Any
fmt.Sprintfor string builder feeding a query is a candidate, in any query language, not only SQL.
How Celvex catches this
Find. Prove. Fix. Verify.
A read-only sweep fingerprints the Dgraph Alpha version through its build and health surface and determines whether ACL is enabled, flagging exposed pre-auth query and mutate endpoints without sending a malicious payload.
Authorized injection of a benign marker predicate through the login or upsert path shows the response shape change, proving reachability without exfiltrating real data. The version and ACL-state evidence becomes an Ed25519-signed Proof Capsule.
The capsule names the steps: upgrade to 25.3.4 or later (or 24.1.9), enable ACL, and put the mutate and GraphQL endpoints behind authentication.
A fresh sweep re-tests the marker injection, confirms it is neutralized on the patched build, and confirms ACL now blocks unauthenticated mutate. The finding closes with a recorded verified-fix event.
The lesson generalizes past this one database. Every query language your stack speaks is an interpreter, and every interpreter that mixes attacker-influenced data with instructions is an injection surface, whether it is SQL, a graph language, a search DSL, or a template. The discipline that beat SQL injection was never about SQL. It was about never letting a value become syntax.
Verifiable security. Find it. Prove it. Fix it. Verify the fix held. That is what we ship.
Sources
- GitLab Advisory: CVE-2026-44840 (Dgraph login-path DQL injection)
- GitLab Advisory: CVE-2026-41327 (Dgraph upsert cond injection)
- NVD: CVE-2026-41328 (Dgraph NQuad Lang injection)
- GHSA-q2m9-6jp9-c6mc (CVE-2026-44840 advisory)
- GHSA-x92x-px7w-4gx4 (CVE-2026-41328 advisory)
- MITRE CWE-943: Improper Neutralization of Special Elements in Data Query Logic
- CELVEX Group: Proof Capsule format
Is your graph database answering unauthenticated queries?
Free Exposure Check, no signup required. We fingerprint your Dgraph nodes, determine ACL state, flag pre-auth query and mutate surfaces, and ship a signed Proof Capsule for the highest-confidence finding.
Run a Free Scan →