OSV 1.4.0 · github-reviewed · 修改于 2026-07-15 04:28
发布时间
2026-07-15 04:28
GitHub 审查时间
2026-07-15 04:28
NVD 发布时间
—
源文件
advisories/github-reviewed/2026/07/GHSA-cm26-5974-52h8/GHSA-cm26-5974-52h8.json
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:
blocklist JSON field into UpdatesResponse.Blocklist (internal/agent/poller.go:33) and then DISCARDS it — poll() applies CertificatePEM, CACertPEM, ConfigYAML, but never references updates.Blocklist (internal/agent/poller.go:300-339).pki.blocklist — pkiSection is only ca/cert/key (internal/configgen/marshal.go:42-46) and GeneratorInput carries no blocklist (internal/configgen/generator.go:23-52). So even the server-rendered config.yml shipped via ConfigYAML cannot 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.
internal/agent/poller.go:33 (decode target), internal/agent/poller.go:300-339 (poll() applies cert/CA/config, never the blocklist).internal/configgen/marshal.go:42-46pkiSection{CA,Cert,Key}internal/configgen/generator.go:23-52GeneratorInputinternal/api/enroll.go:255-330renderHostConfiginternal/store/sqlite.go:2034 (GetBlocklistForCA), internal/api/updates.go:182-191 (resp.Blocklist), internal/api/updates.go:277 (has_updates set on blocklist change).internal/pki/blocklist.go (Blocklist type) is never used in non-test code — no server-side enforcement either.blocklist table.GET /api/v1/agent/updates; server returns blocklist: [<B-fp>, ...] and has_updates=true.Blocklist then discards it; poll() has no blocklist branch.configgen.Generate emits pki: {ca,cert,key} with no blocklist key (proven by PoC).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.
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.
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.
Blocklist []safeString to pkiSection (yaml blocklist,omitempty) and GeneratorInput; consider also pki.disconnect_invalid: true.updates.Blocklist by re-rendering/rewriting config.yml + SIGHUP (same path as ConfigYAML). Simplest: fold the blocklist into the server-rendered ConfigYAML so it flows through the existing write path.pki.blocklist entry in the agent's written config.yml.