Know every vulnerabilitybefore it knows you.
DevGuard continuously monitors your dependencies and alerts you when CVEs like this one affect your stack — with real-time threat intelligence built for developers.
GHSA-cm26-5974-52h8
No affected components available
Summary
nebula-mesh revokes a host by adding its certificate fingerprint to a per-CA blocklist and shipping that list to every other agent on each poll. Slack's Nebula enforces certificate revocation ONLY through the pki.blocklist list in config.yml (no CRL/OCSP). The project's own code states this: internal/pki/durations.go:15 — "Revocation via the blocklist remains the immediate security control."
The server side is fully implemented (computes per-CA blocklist via GetBlocklistForCA, returns it in the agent-updates response, sets has_updates=true when non-empty). The agent side was never implemented:
- The agent decodes the
blocklistJSON field intoUpdatesResponse.Blocklist(internal/agent/poller.go:33) and then DISCARDS it —poll()appliesCertificatePEM,CACertPEM,ConfigYAML, but never referencesupdates.Blocklist(internal/agent/poller.go:300-339). - The config generator has NO field to emit
pki.blocklist—pkiSectionis onlyca/cert/key(internal/configgen/marshal.go:42-46) andGeneratorInputcarries no blocklist (internal/configgen/generator.go:23-52). So even the server-renderedconfig.ymlshipped viaConfigYAMLcannot carry it.
Result: a blocked/offboarded/compromised host's certificate is never rejected by its peers. Its handshakes keep succeeding for the full remaining cert lifetime — up to 30 days for agent hosts (DefaultAgentCertDuration) and 365 days for mobile hosts (DefaultMobileCertDuration). Blocking a host in the UI/API has no effect on the data plane.
Affected components
- Agent drops the blocklist:
internal/agent/poller.go:33(decode target),internal/agent/poller.go:300-339(poll() applies cert/CA/config, never the blocklist). - Generator cannot emit it:
internal/configgen/marshal.go:42-46(pkiSection{CA,Cert,Key}),internal/configgen/generator.go:23-52(GeneratorInputhas no blocklist),internal/api/enroll.go:255-330(renderHostConfig, source of shipped ConfigYAML). - Server correctly produces/ships it (proves intent):
internal/store/sqlite.go:2034(GetBlocklistForCA),internal/api/updates.go:182-191(resp.Blocklist),internal/api/updates.go:277(has_updatesset on blocklist change). - Dead helper:
internal/pki/blocklist.go(Blocklisttype) is never used in non-test code — no server-side enforcement either.
Reachability (hop by hop)
- Operator clicks Block on host B (or B is compromised/offboarded). B's fingerprint enters the per-CA
blocklisttable. - Every other host A under the same CA polls
GET /api/v1/agent/updates; server returnsblocklist: [<B-fp>, ...]andhas_updates=true. - A's agent decodes
Blocklistthen discards it;poll()has no blocklist branch. - Even on a config re-render,
configgen.Generateemitspki: {ca,cert,key}with noblocklistkey (proven by PoC). - A's Nebula daemon has an empty blocklist and accepts handshakes from B's still-valid cert. B keeps full mesh access.
Impact
Revocation is the only in-band mechanism that isolates a compromised/offboarded host from a Nebula mesh. Because the blocklist never reaches any peer's config.yml, a Blocked host retains full overlay reachability to every peer under its CA (and internal services on the mesh) for up to 30d (agent) / 365d (mobile). An attacker who exfiltrates host.key+host.crt can run stock slackhq/nebula directly, ignore the agent's 403/410 poll responses, and stay connected after the operator revokes the host. Operator-visible state (UI shows blocked, audit log records it) is misleading.
Proof of Concept (benign)
internal/configgen/blocklist_poc_test.go renders a fully-populated host config and asserts the output contains the pki section but NO blocklist key:
$ go test ./internal/configgen/ -run TestPoC_NMESH001 -v
=== RUN TestPoC_NMESH001_GeneratedConfigOmitsBlocklist
CONFIRMED: generated config has a pki section but no blocklist key
pki:
ca: /etc/nebula/ca.crt
cert: /etc/nebula/host.crt
key: /etc/nebula/host.key
...
--- PASS
The agent half is verifiable by inspection: poll() has branches for CertificatePEM/CACertPEM/ConfigYAML/RekeyRequired but none for Blocklist.
Distinctness
NOT a duplicate of GHSA-339v / CVE-2026-53602 (revocation durability = a blocked host getting a NEW cert re-issued; its fix CheckIssuanceAllowed is present and orthogonal). This bug is that the EXISTING cert is never rejected at peers — the distribution/enforcement layer. Checked against all 17 known advisories; none cover blocklist application in the agent or pki.blocklist generation.
Remediation
- Add
Blocklist []safeStringtopkiSection(yamlblocklist,omitempty) andGeneratorInput; consider alsopki.disconnect_invalid: true. - Have the agent apply
updates.Blocklistby re-rendering/rewritingconfig.yml+ SIGHUP (same path asConfigYAML). Simplest: fold the blocklist into the server-renderedConfigYAMLso it flows through the existing write path. - Add a regression test asserting a non-empty server blocklist yields a
pki.blocklistentry in the agent's written config.yml.
The vulnerability can be exploited over the network without needing physical access. It is easy for an attacker to exploit this vulnerability. An attacker needs basic access or low-level privileges. No user interaction is needed for the attacker to exploit this vulnerability. The impact is confined to the system where the vulnerability exists. There is a high impact on the confidentiality of the information. There is a high impact on the integrity of the data.
Exploitation activity has been observed. Apply available patches or mitigations urgently.
Probability that this vulnerability will be exploited in the wild within the next 30 days.
We did not find any exploit available. Neither in GitHub repositories nor in the Exploit-Database.
Browse More
Continuously monitor your dependencies and get alerted when vulnerabilities like this one affect your stack.
Checkout DevGuard