GHSA-7HP6-G3PQ-3PC3
Vulnerability from github – Published: 2026-06-08 23:08 – Updated: 2026-06-08 23:08internal/configgen/generator.go:86,108,119 interpolates the operator-supplied ListenHost and TunDevice fields raw into a text/template that produces the agent's config.yml. internal/web/advanced.go:20-35 accepts both with only strings.TrimSpace — no character or shape validation.
Exploit
An operator (or attacker with any operator key, given the cross-tenant CRUD advisory) sets adv_tun_device to:
nebula0
lighthouse:
am_lighthouse: true
hosts: ["10.0.0.1"]
#
The agent fetches the rendered config on its next signed poll. On config reload, it loads the injected YAML keys: the host self-promotes to lighthouse, attracts mesh traffic, or sets am_relay: true to be selected as a relay. The ListenHost field has the same shape.
Affected
All released versions prior to v0.3.2.
Threat model
- Today: operator can compromise their own host's config (trivially allowed if they own the host, but they can also set lighthouse/relay flags that the operator-create form does NOT expose — privilege uplift within their own tenant).
- Combined with the critical /api/v1 authz advisory: any operator key can mutate ANOTHER tenant's host overrides and inject YAML there.
- Post-fix of the authz advisory: still relevant — the agent unconditionally trusts whatever config the server hands it, so any future operator-impersonation bug re-amplifies this.
Suggested fix
Two options, either acceptable:
- Input validation in
parseAdvancedFromForm(internal/web/advanced.go): ListenHost: regex^[A-Za-z0-9.:\[\]_-]+$(IPv4/IPv6/hostname)-
TunDevice: regex^[A-Za-z0-9_-]{1,15}$(Linux IFNAMSIZ caps at 15) Reject invalid input with a form-level error; do not write to the host row. -
Safer marshalling: switch
configgen/generator.goto marshal a typed Go struct viagopkg.in/yaml.v3(which escapes correctly) instead oftext/templatestring-concat. Larger change, but eliminates this entire injection class.
Option 2 is preferable long-term. Option 1 is the quick fix.
The unsafe_routes advanced field is already netip.Parse{Prefix,Addr}-validated at enroll.go:226-233 — apply the same validation discipline to the other advanced fields.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/juev/nebula-mesh"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.3.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-47722"
],
"database_specific": {
"cwe_ids": [
"CWE-94"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-08T23:08:15Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "`internal/configgen/generator.go:86,108,119` interpolates the operator-supplied `ListenHost` and `TunDevice` fields raw into a `text/template` that produces the agent\u0027s `config.yml`. `internal/web/advanced.go:20-35` accepts both with only `strings.TrimSpace` \u2014 no character or shape validation.\n\n## Exploit\nAn operator (or attacker with any operator key, given the cross-tenant CRUD advisory) sets `adv_tun_device` to:\n\n```\nnebula0\nlighthouse:\n am_lighthouse: true\n hosts: [\"10.0.0.1\"]\n#\n```\n\nThe agent fetches the rendered config on its next signed poll. On config reload, it loads the injected YAML keys: the host self-promotes to lighthouse, attracts mesh traffic, or sets `am_relay: true` to be selected as a relay. The `ListenHost` field has the same shape.\n\n## Affected\nAll released versions prior to v0.3.2.\n\n## Threat model\n- **Today**: operator can compromise their own host\u0027s config (trivially allowed if they own the host, but they can also set lighthouse/relay flags that the operator-create form does NOT expose \u2014 privilege uplift within their own tenant).\n- **Combined with the critical /api/v1 authz advisory**: any operator key can mutate ANOTHER tenant\u0027s host overrides and inject YAML there.\n- **Post-fix of the authz advisory**: still relevant \u2014 the agent unconditionally trusts whatever config the server hands it, so any future operator-impersonation bug re-amplifies this.\n\n## Suggested fix\nTwo options, either acceptable:\n\n1. **Input validation** in `parseAdvancedFromForm` (`internal/web/advanced.go`):\n - `ListenHost`: regex `^[A-Za-z0-9.:\\[\\]_-]+$` (IPv4/IPv6/hostname)\n - `TunDevice`: regex `^[A-Za-z0-9_-]{1,15}$` (Linux IFNAMSIZ caps at 15)\n Reject invalid input with a form-level error; do not write to the host row.\n\n2. **Safer marshalling**: switch `configgen/generator.go` to marshal a typed Go struct via `gopkg.in/yaml.v3` (which escapes correctly) instead of `text/template` string-concat. Larger change, but eliminates this entire injection class.\n\nOption 2 is preferable long-term. Option 1 is the quick fix.\n\nThe `unsafe_routes` advanced field is already `netip.Parse{Prefix,Addr}`-validated at `enroll.go:226-233` \u2014 apply the same validation discipline to the other advanced fields.",
"id": "GHSA-7hp6-g3pq-3pc3",
"modified": "2026-06-08T23:08:15Z",
"published": "2026-06-08T23:08:15Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/juev/nebula-mesh/security/advisories/GHSA-7hp6-g3pq-3pc3"
},
{
"type": "WEB",
"url": "https://github.com/forgekeep/nebula-mesh/issues/126"
},
{
"type": "WEB",
"url": "https://github.com/forgekeep/nebula-mesh/commit/c1506f7344ab375a145a7449b193af3f19bb41ef"
},
{
"type": "PACKAGE",
"url": "https://github.com/juev/nebula-mesh"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "nebula-mesh: Host advanced overrides allow YAML injection into agent config.yml"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.