Skip to main content

nebula-mesh CVE-2026-47726

| EUVDEUVD-2026-49942 HIGH
Improper Authorization (CWE-285)
2026-06-08 https://github.com/juev/nebula-mesh GHSA-qm33-p5p9-f8vg
7.1
CVSS 4.0 · Vendor: https://github.com/juev/nebula-mesh
Share

Severity by source

Vendor (https://github.com/juev/nebula-mesh) PRIMARY
7.1 HIGH
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
vuln.today AI
6.5 MEDIUM

Network-reachable endpoint needs any valid operator key (PR:L) with low complexity; impact is cross-tenant data disclosure only, so C:H with I:N/A:N.

3.1 AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
4.0 AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
SUSE
HIGH
qualitative

Primary rating from Vendor (https://github.com/juev/nebula-mesh).

CVSS VectorVendor: https://github.com/juev/nebula-mesh

Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
X

Lifecycle Timeline

5
Analysis Updated
Jul 28, 2026 - 19:27 vuln.today
v2 (cvss_changed)
Re-analysis Queued
Jul 28, 2026 - 19:22 vuln.today
cvss_changed
CVSS changed
Jul 28, 2026 - 19:22 NVD
7.1 (HIGH)
Source Code Evidence Fetched
Jun 09, 2026 - 00:13 vuln.today
Analysis Generated
Jun 09, 2026 - 00:13 vuln.today

DescriptionCVE.org

internal/api/audit.go:12 - handleGetAuditLog does no admin check. The route is bearer-auth gated only; any operator API key returns the full audit log via store.ListAuditEntries (up to limit=1000). This includes cross-tenant actor names, host/CA/operator IDs, action timestamps, and masked-IP entries from rate-limit refusals - enough surface for a tenant to enumerate the server's activity, infer staffing patterns, or identify high-value targets.

Affected

All released versions up to v0.3.1.

Reproducer

curl -H "Authorization: Bearer <any-operator-key>" \
  https://server/api/v1/audit-log?limit=1000

Suggested fix

Two options, either acceptable:

  1. if !actorIsAdmin(ctx) { 403 } - strictest; matches the "operator management is admin-only" stance.
  2. Scope to actor: filter store.ListAuditEntries by actor.Username plus a subquery of CA IDs the actor owns. Operators see their own audit entries plus entries against their CA's resources.

Recommend option 1 unless the UI needs per-operator audit views.

Suggested patch

Verified locally: go vet, go test -race -count=1 ./..., golangci-lint v2.12 all clean.

diff
diff --git a/internal/api/audit.go b/internal/api/audit.go
index 3236631..57b57ce 100644
--- a/internal/api/audit.go
+++ b/internal/api/audit.go
@@ -10,6 +10,10 @@ import (
 const defaultAuditLimit = 100

 func (s *Server) handleGetAuditLog(w http.ResponseWriter, r *http.Request) {
+	if !actorIsAdmin(r.Context()) {
+		writeError(w, http.StatusForbidden, "audit log access requires the admin role")
+		return
+	}
 	filter := store.AuditFilter{
 		Action: r.URL.Query().Get("action"),
 		Limit:  defaultAuditLimit,
diff --git a/internal/api/audit_admin_test.go b/internal/api/audit_admin_test.go
new file mode 100644
index 0000000..47e1ca4
--- /dev/null
+++ b/internal/api/audit_admin_test.go
@@ -0,0 +1,62 @@
+package api
+
+import (
+	"context"
+	"crypto/sha256"
+	"encoding/hex"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/google/uuid"
+	"github.com/juev/nebula-mesh/internal/models"
+)
+
+// TestHandleGetAuditLog_NonAdminForbidden confirms a non-admin operator
+// API key cannot read the audit log. The legacy config-key path stays
+// admin and is covered by the happy-path test elsewhere.
+func TestHandleGetAuditLog_NonAdminForbidden(t *testing.T) {
+	srv, _ := newTestServer(t)
+
+	nonAdminKey := uuid.New().String()
+	keyHash := sha256.Sum256([]byte(nonAdminKey))
+	if err := srv.store.CreateOperator(context.Background(), &models.Operator{
+		ID: uuid.New().String(), Username: "non-admin", PasswordHash: "x",
+		Role: "user", Status: models.OperatorStatusActive,
+	}); err != nil {
+		t.Fatal(err)
+	}
+	op, err := srv.store.GetOperatorByUsername(context.Background(), "non-admin")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := srv.store.CreateOperatorAPIKey(context.Background(), &models.OperatorAPIKey{
+		ID: uuid.New().String(), OperatorID: op.ID, KeyHash: hex.EncodeToString(keyHash[:]),
+	}); err != nil {
+		t.Fatal(err)
+	}
+
+	req := httptest.NewRequest("GET", "/api/v1/audit-log", nil)
+	req.Header.Set("Authorization", "Bearer "+nonAdminKey)
+	rec := httptest.NewRecorder()
+	srv.ServeHTTP(rec, req)
+
+	if rec.Code != http.StatusForbidden {
+		t.Errorf("non-admin audit-log status = %d, want 403", rec.Code)
+	}
+}
+
+// TestHandleGetAuditLog_LegacyKeyAllowed confirms the legacy config-key
+// path still reaches the handler (preserves backward compatibility).
+func TestHandleGetAuditLog_LegacyKeyAllowed(t *testing.T) {
+	srv, _ := newTestServer(t)
+
+	req := httptest.NewRequest("GET", "/api/v1/audit-log", nil)
+	req.Header.Set("Authorization", "Bearer "+testAPIKey)
+	rec := httptest.NewRecorder()
+	srv.ServeHTTP(rec, req)
+
+	if rec.Code == http.StatusForbidden {
+		t.Errorf("legacy key rejected with 403; want pass-through")
+	}
+}

AnalysisAI

Broken authorization in nebula-mesh (Go, all versions up to and including v0.3.1) lets any authenticated operator read the entire server-wide audit log. The GET /api/v1/audit-log endpoint (handleGetAuditLog in internal/api/audit.go) is protected only by bearer-token authentication and omits the admin role check, so a low-privileged operator key returns up to 1000 cross-tenant entries including actor names, host/CA/operator IDs, timestamps, and masked-IP records. A working one-line curl reproducer is published in the GHSA advisory (publicly available exploit code exists); it is not in CISA KEV and EPSS is low at 0.04%.

Technical ContextAI

nebula-mesh is a self-hosted control-plane server for managing Nebula overlay-mesh certificate authorities, hosts, and operators, written in Go (module github.com/juev/nebula-mesh, CPE pkg:go/github.com_juev_nebula-mesh). The flaw is a CWE-285 Improper Authorization defect: the multi-tenant model treats operator management as admin-only, and audit data spans all tenants, but the audit-log route enforces only authentication (bearerAuth middleware) and never calls actorIsAdmin. store.ListAuditEntries therefore returns unscoped results to any caller holding a valid operator API key, disclosing data across CA/tenant boundaries.

RemediationAI

Vendor-released patch: upgrade to nebula-mesh v0.3.2, which adds an actorIsAdmin check to handleGetAuditLog and returns 403 to non-admin operators (fix commit https://github.com/forgekeep/nebula-mesh/commit/8baaace54c2a23e7c351b3efab5a31ab07b125dc; advisory GHSA-qm33-p5p9-f8vg). If you cannot upgrade immediately, restrict who holds operator API keys and rotate or revoke keys issued to untrusted tenants, since any valid operator key triggers the disclosure. As a network compensating control, place the /api/v1/audit-log route behind a reverse-proxy ACL that permits only admin source IPs, accepting that this breaks any legitimate per-operator audit access; alternatively apply the vendor's option-2 approach of scoping ListAuditEntries to the actor's own username and owned CA IDs if per-operator audit views are required.

Vendor StatusVendor

SUSE

Severity: Important
Product Status
SUSE Linux Enterprise Server 16.1 Affected
SUSE Linux Enterprise Server for SAP applications 16.1 Affected
SUSE Linux Enterprise Module for Package Hub 15 SP5 Affected
SUSE Linux Enterprise Module for Package Hub 15 SP6 Affected
openSUSE Leap 15.5 Affected

Share

CVE-2026-47726 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy