github.com/dgraph-io/dgraph/v25

CVE-2026-41328

CVE-2026-41328 is a critical-severity security vulnerability in github.com/dgraph-io/dgraph/v25 (go), affecting versions < 25.3.3. It is fixed in 25.3.3.

Key facts
CVSS score
9.1
Critical
Attack vector
Network
Issuing authority
GitHub Advisory Database
Affected package
github.com/dgraph-io/dgraph/v25
Fixed in
25.3.3
Disclosed
2026

Summary

Executive Summary A vulnerability has been found in Dgraph that gives an unauthenticated attacker full read access to every piece of data in the database. This affects Dgraph's default configuration where ACL is not enabled. The attack requires two HTTP POSTs to port 8080. The first sets up a schema predicate with @unique @index(exact) @lang via /alter (also unauthenticated in default config). The second sends a crafted JSON mutation to /mutate?commitNow=true where a JSON key contains the predicate name followed by @ and a DQL injection payload in the language tag position. The injection exploits the addQueryIfUnique function in edgraph/server.go, which constructs DQL queries using fmt.Sprintf with unsanitized predicateName that includes the raw pred.Lang value. The Lang field is extracted from JSON mutation keys by x.PredicateLang(), which splits on @, and is never validated by any function in the codebase. The attacker injects a closing parenthesis to escape the eq() function, adds an arbitrary named query block, and uses a # comment to neutralize trailing template syntax. The injected query executes server-side and its results are returned in the HTTP response. POC clip: https://github.com/user-attachments/assets/bbfb7bba-c957-4b57-b534-48a958314186 CVSS Score CVSS 3.1: 9.1 (Critical) | Metric | Value | Rationale | | ------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------- | | Attack Vector | Network | HTTP POST to port 8080 | | Attack Complexity | Low | Two requests, deterministic outcome, no special conditions | | Privileges Required | None | No authentication when ACL is disabled (default) | | User Interaction | None | Fully automated | | Scope | Unchanged | Stays within the Dgraph data layer | | Confidentiality | High | Full database exfiltration: all nodes, all predicates, all values | | Integrity | High | The mutation that carries the injection also writes data; the attacker can also set up arbitrary schema via unauthenticated /alter | | Availability | None | No denial of service | Vulnerability Summary | Field | Value | | ----- | --------------------------------------------------------------------------- | | Title | Pre-Auth DQL Injection via Unsanitized NQuad Lang Field in addQueryIfUnique | | Type | Injection | | CWE | CWE-943 (Improper Neutralization of Special Elements in Data Query Logic) | | CVSS | 9.8 | Target Information | Field | Value | | --------------------- | ------------------------------------------------------------------------------------------------------------------------ | | Project | Dgraph | | Repository | https://github.com/dgraph-io/dgraph | | Tested version | v25.3.0 | | Lang split | x/x.go line 919 (PredicateLang splits on @, returns everything after as Lang) | | Lang assignment | chunker/jsonparser.go line 524 (nq.Predicate, nq.Lang = x.PredicateLang(nq.Predicate)) | | Validation gap | edgraph/server.go line 2142 (validateKeys checks nq.Predicate only, never nq.Lang) | | Injection sink | edgraph/server.go line 1808 (fmt.Sprintf with predicateName containing raw pred.Lang) | | predicateName build | edgraph/server.go line 1780 (fmt.Sprintf("%v@%v", predicateName, pred.Lang)) | | Auth bypass (query) | edgraph/access.go line 958 (authorizeQuery returns nil when AclSecretKey == nil) | | Auth bypass (mutate) | edgraph/access.go line 788 (authorizeMutation returns nil when AclSecretKey == nil) | | Response exfiltration | dgraph/cmd/alpha/http.go line 498 (mp["queries"] = json.RawMessage(resp.Json)) | | HTTP port | 8080 (default) | | Prerequisite | A predicate with @unique @index(exact) @lang in the schema. The attacker can create this via unauthenticated /alter. | Test Environment | Component | Version / Details | | -------------- | ------------------------------------------------------------------ | | Host OS | macOS (darwin 25.3.0) | | Dgraph | v25.3.0 via dgraph/dgraph:latest Docker image | | Docker Compose | 1 Zero + 1 Alpha, default config, whitelist=0.0.0.0/0 | | Python | 3.x with requests | | Network | localhost (127.0.0.1) | Vulnerability Detail Location: edgraph/server.go lines 1778-1808 (addQueryIfUnique) CWE: CWE-943 (Improper Neutralization of Special Elements in Data Query Logic) The /mutate endpoint accepts JSON mutations. When a predicate has the @unique directive, the addQueryIfUnique function builds a DQL query to check whether the value already exists. The JSON chunker at jsonparser.go:524 splits mutation keys on @ via x.PredicateLang: PredicateLang at x/x.go:919 splits on the last @ and returns everything after it as the Lang string with no validation: validateKeys at server.go:2142 validates only nq.Predicate. It never touches nq.Lang: addQueryIfUnique at server.go:1778-1808 builds predicateName from the predicate and the raw Lang, then interpolates it into a DQL query via fmt.Sprintf: There is no escaping, no parameterization, no structural validation, and no character allowlist applied to pred.Lang anywhere between the HTTP input and the fmt.Sprintf query construction. An attacker crafts a JSON mutation key: After PredicateLang splits on @: Predicate = name (passes all validation) Lang = en,"x")) leak(func: has(dgraph.type)) { ... } } # (never validated) The constructed DQL becomes: The # comment neutralizes any trailing syntax from the template. The DQL parser accepts this as two valid query blocks: a var query (returns empty) and a named leak query that exfiltrates all data. The uniqueness check passes (no existing name@en equals "x"), so the mutation succeeds, and the injected query results are returned in data.queries.leak. Full Chain Explanation The attacker has no Dgraph credentials and no prior access to the server. Step 1. The attacker creates the required schema via unauthenticated /alter: No X-Dgraph-AccessToken header. In default configuration, /alter has no authentication when ACL is disabled. Step 2. The attacker sends the injection payload: Step 3. mutationHandler at http.go:345 parses the JSON body. The key name@en,... is treated as predicate name with language tag en,"x")) leak(...) } } #. Step 4. x.PredicateLang at x.go:919 splits the key on the last @. The Predicate is name. The Lang is the injection payload. Step 5. validateKeys at server.go:2142 validates only nq.Predicate (name), which passes. nq.Lang is never checked. Step 6. addQueryIfUnique at server.go:1778 constructs predicateName by appending the raw pred.Lang at line 1780. At line 1808, fmt.Sprintf interpolates this into the DQL query string. Step 7. dql.ParseWithNeedVars parses the constructed DQL. It encounters the original var query and the injected leak query. Both are accepted as valid DQL. Step 8. authorizeQuery at access.go:958 returns nil because AclSecretKey == nil (default). No predicate-level authorization is performed. Step 9. processQuery executes both queries. The leak block traverses every node with a dgraph.type predicate and returns all requested fields. Step 10. The response is returned to the attacker at http.go:498. The data.queries.leak array contains every matching node with all their predicates. Proof of Concept Files | File | Purpose | | ------------------ | ------------------------------------------------------------ | | report.md | This vulnerability report | | poc.py | Exploit: sets up schema, seeds data, injects, prints leak | | docker-compose.yml | Spins up a Dgraph cluster (1 Zero + 1 Alpha, default config) | | DGraphPreAuthLangDQL.mp4 | Screen recording of the full attack from start to exfiltration | ZIP with all the relevant files: DGraphPreAuthDQLLang.zip poc.py The exploit performs three operations: (1) creates the @unique @index(exact) @lang schema, (2) seeds test data including user secrets and AWS credentials, (3) sends the injection mutation and prints all exfiltrated records. Tested Output Steps to Reproduce Prerequisites Python 3 with requests (pip install requests) Docker and Docker Compose Step 1: Start Dgraph Wait for health: Step 2: Run the exploit The PoC handles schema creation, data seeding, and exploitation automatically. Step 3: Manual reproduction To reproduce manually without the PoC script: What to verify HTTP POST returns 200 (endpoint is reachable without auth) Response contains data.queries.leak with an array of nodes The nodes include secrets, AWS credentials, and other data the attacker never queried through legitimate means The mutation also succeeds (a new node is created), confirming that the injection does not break the mutation flow Mitigations and Patch Location: edgraph/server.go, addQueryIfUnique (line 1778) and x/x.go, PredicateLang (line 919) Validate nq.Lang: Add validation in validateKeys (or a new validateLang function) that restricts the Lang field to BCP 47 language tags: ^[a-zA-Z]{2,3}(-[a-zA-Z0-9]+)*$. Reject any Lang value containing parentheses, braces, quotes, #, newlines, or other DQL-significant characters. Parameterize DQL queries: Replace the fmt.Sprintf query construction in addQueryIfUnique with a structured query builder that constructs DQL AST nodes programmatically. This eliminates the injection surface entirely because the predicate name is passed as a typed value rather than interpolated as a raw string. Escape at the sink: If parameterization is not immediately feasible, escape DQL-significant characters (), {, }, ", #, newlines) in both predicateName and val before interpolation at line 1808. Defense in depth: After query construction, validate that the resulting DQL contains exactly the expected number of root query blocks. The uniqueness check should produce exactly one var(...) block per unique predicate. Any additional blocks indicate injection.

Impact

Severity and exposure

CVE-2026-41328 has a CVSS score of 9.1 (Critical). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment.

A fixed version is available (25.3.3). Upgrading removes the vulnerable code path.

Affected versions

go

  • github.com/dgraph-io/dgraph/v25 (< 25.3.3)
  • github.com/dgraph-io/dgraph/v24 (<= 24.1.8)
  • github.com/dgraph-io/dgraph (<= 1.2.8)

Security releases

  • github.com/dgraph-io/dgraph/v25 → 25.3.3 (go)
Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter instead of chasing every advisory.

Kodem's runtime-powered SCA identifies whether CVE-2026-41328 is reachable in your applications. Explore open-source security for your team.

See if CVE-2026-41328 is reachable in your applications. Get a demo

Already deployed Kodem? See CVE-2026-41328 in your environment

Remediation advice

Upgrade github.com/dgraph-io/dgraph/v25 to 25.3.3 or later to resolve this vulnerability.

Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.

Frequently asked questions about CVE-2026-41328

What is CVE-2026-41328?

CVE-2026-41328 is a critical-severity security vulnerability in github.com/dgraph-io/dgraph/v25 (go), affecting versions < 25.3.3. It is fixed in 25.3.3.

How severe is CVE-2026-41328?

CVE-2026-41328 has a CVSS score of 9.1 (Critical). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.

Which packages are affected by CVE-2026-41328?
  • github.com/dgraph-io/dgraph/v25 (go) (versions < 25.3.3)
  • github.com/dgraph-io/dgraph/v24 (go) (versions <= 24.1.8)
  • github.com/dgraph-io/dgraph (go) (versions <= 1.2.8)
Is there a fix for CVE-2026-41328?

Yes. CVE-2026-41328 is fixed in 25.3.3. Upgrade to this version or later.

Is CVE-2026-41328 exploitable, and should I be worried?

Whether CVE-2026-41328 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo

What actually determines whether CVE-2026-41328 is exploitable, and how bad it is?

Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.

How do I fix CVE-2026-41328?

Upgrade github.com/dgraph-io/dgraph/v25 to 25.3.3 or later.

Stop the waste.
Protect your environment with Kodem.