VYPR
Critical severity10.0NVD Advisory· Published Apr 6, 2026· Updated Apr 22, 2026

CVE-2026-34976

CVE-2026-34976

Description

Dgraph is an open source distributed GraphQL database. Prior to 25.3.1, the restoreTenant admin mutation is missing from the authorization middleware config (admin.go), making it completely unauthenticated. Unlike the similar restore mutation which requires Guardian-of-Galaxy authentication, restoreTenant executes with zero middleware. This mutation accepts attacker-controlled backup source URLs (including file:// for local filesystem access), S3/MinIO credentials, encryption key file paths, and Vault credential file paths. An unauthenticated attacker can overwrite the entire database, read server-side files, and perform SSRF. This vulnerability is fixed in 25.3.1.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/dgraph-io/dgraph/v25Go
< 25.3.125.3.1
github.com/dgraph-io/dgraph/v24Go
<= 24.0.5
github.com/dgraph-io/dgraphGo
<= 1.2.8

Affected products

1
  • cpe:2.3:a:dgraph:dgraph:*:*:*:*:*:go:*:*
    Range: <=25.3.0

Patches

1
b15c87e9353e

Merge commit from fork

https://github.com/dgraph-io/dgraphMatthew McNeelyApr 1, 2026via ghsa
2 files changed · +116 0
  • graphql/admin/admin_auth_test.go+115 0 added
    @@ -0,0 +1,115 @@
    +/*
    + * SPDX-FileCopyrightText: © 2017-2025 Istari Digital, Inc.
    + * SPDX-License-Identifier: Apache-2.0
    + */
    +
    +package admin
    +
    +import (
    +	"reflect"
    +	"testing"
    +
    +	"github.com/stretchr/testify/assert"
    +	"github.com/stretchr/testify/require"
    +
    +	"github.com/dgraph-io/dgraph/v25/graphql/resolve"
    +)
    +
    +// mwPointer returns the function pointer for a middleware so it can be compared
    +// by identity rather than value (Go prohibits direct function equality checks).
    +func mwPointer(mw resolve.MutationMiddleware) uintptr {
    +	return reflect.ValueOf(mw).Pointer()
    +}
    +
    +func containsMW(mws resolve.MutationMiddlewares, target resolve.MutationMiddleware) bool {
    +	want := mwPointer(target)
    +	for _, mw := range mws {
    +		if mwPointer(mw) == want {
    +			return true
    +		}
    +	}
    +	return false
    +}
    +
    +// TestAdminMutationMiddlewareConfig asserts the security posture for every
    +// registered admin mutation.
    +//
    +// Each mutation must be present in adminMutationMWConfig. An absent entry
    +// causes the middleware chain to be empty, bypassing all authentication,
    +// IP whitelisting, and audit logging (see resolve/middlewares.go: Then()).
    +func TestAdminMutationMiddlewareConfig(t *testing.T) {
    +	type securityRequirements struct {
    +		// desc is shown in failure messages to explain why this mutation needs these middlewares.
    +		desc string
    +		// ipWhitelist: must include IpWhitelistingMW4Mutation
    +		ipWhitelist bool
    +		// superAdminAuth: must include GuardianOfTheGalaxyAuthMW4Mutation
    +		superAdminAuth bool
    +		// guardianAuth: must include GuardianAuthMW4Mutation
    +		guardianAuth bool
    +		// aclOnly: must include AclOnlyMW4Mutation
    +		aclOnly bool
    +	}
    +
    +	tests := map[string]securityRequirements{
    +		// Superadmin (Guardian-of-Galaxy) auth — highest privilege operations.
    +		"backup":     {desc: "database backups", ipWhitelist: true, superAdminAuth: true},
    +		"config":     {desc: "cluster config changes", ipWhitelist: true, superAdminAuth: true},
    +		"draining":   {desc: "draining mode", ipWhitelist: true, superAdminAuth: true},
    +		"restore":    {desc: "backup restore", ipWhitelist: true, superAdminAuth: true},
    +		"restoreTenant": { // CVE: previously absent from this map (CVSS 10.0)
    +			desc:           "cross-namespace backup restore — accepts attacker-controlled URLs",
    +			ipWhitelist:    true,
    +			superAdminAuth: true,
    +		},
    +		"shutdown":   {desc: "node shutdown", ipWhitelist: true, superAdminAuth: true},
    +		"removeNode": {desc: "cluster topology change", ipWhitelist: true, superAdminAuth: true},
    +		"moveTablet": {desc: "tablet relocation", ipWhitelist: true, superAdminAuth: true},
    +		"assign":     {desc: "UID/timestamp assignment", ipWhitelist: true, superAdminAuth: true},
    +
    +		// Superadmin + ACL — namespace lifecycle mutations.
    +		"addNamespace":    {desc: "namespace creation", ipWhitelist: true, superAdminAuth: true, aclOnly: true},
    +		"deleteNamespace": {desc: "namespace deletion", ipWhitelist: true, superAdminAuth: true, aclOnly: true},
    +		"resetPassword":   {desc: "password reset", ipWhitelist: true, superAdminAuth: true, aclOnly: true},
    +
    +		// Guardian auth — standard admin operations.
    +		"export":          {desc: "data export", ipWhitelist: true, guardianAuth: true},
    +		"updateGQLSchema": {desc: "GraphQL schema update", ipWhitelist: true, guardianAuth: true},
    +
    +		// Minimal (IP whitelist + logging only) — dgraph handles auth internally for these.
    +		"login":       {desc: "login (auth handled internally)", ipWhitelist: true},
    +		"addUser":     {desc: "user management (dgraph handles guardian auth)", ipWhitelist: true},
    +		"addGroup":    {desc: "group management (dgraph handles guardian auth)", ipWhitelist: true},
    +		"updateUser":  {desc: "user management (dgraph handles guardian auth)", ipWhitelist: true},
    +		"updateGroup": {desc: "group management (dgraph handles guardian auth)", ipWhitelist: true},
    +		"deleteUser":  {desc: "user management (dgraph handles guardian auth)", ipWhitelist: true},
    +		"deleteGroup": {desc: "group management (dgraph handles guardian auth)", ipWhitelist: true},
    +	}
    +
    +	for mutation, req := range tests {
    +		t.Run(mutation, func(t *testing.T) {
    +			mws, ok := adminMutationMWConfig[mutation]
    +			require.Truef(t, ok,
    +				"mutation %q (%s) is missing from adminMutationMWConfig — "+
    +					"absent entries bypass ALL authentication, IP whitelisting, and audit logging",
    +				mutation, req.desc)
    +
    +			if req.ipWhitelist {
    +				assert.Truef(t, containsMW(mws, resolve.IpWhitelistingMW4Mutation),
    +					"mutation %q (%s) must include IpWhitelistingMW4Mutation", mutation, req.desc)
    +			}
    +			if req.superAdminAuth {
    +				assert.Truef(t, containsMW(mws, resolve.GuardianOfTheGalaxyAuthMW4Mutation),
    +					"mutation %q (%s) must include GuardianOfTheGalaxyAuthMW4Mutation", mutation, req.desc)
    +			}
    +			if req.guardianAuth {
    +				assert.Truef(t, containsMW(mws, resolve.GuardianAuthMW4Mutation),
    +					"mutation %q (%s) must include GuardianAuthMW4Mutation", mutation, req.desc)
    +			}
    +			if req.aclOnly {
    +				assert.Truef(t, containsMW(mws, resolve.AclOnlyMW4Mutation),
    +					"mutation %q (%s) must include AclOnlyMW4Mutation", mutation, req.desc)
    +			}
    +		})
    +	}
    +}
    \ No newline at end of file
    
  • graphql/admin/admin.go+1 0 modified
    @@ -503,6 +503,7 @@ var (
     		"export":          stdAdminMutMWs, // dgraph handles the export for other namespaces by superadmin
     		"login":           minimalAdminMutMWs,
     		"restore":         gogMutMWs,
    +		"restoreTenant":   gogMutMWs,
     		"shutdown":        gogMutMWs,
     		"removeNode":      gogMutMWs,
     		"moveTablet":      gogMutMWs,
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.