Back to Networking Knowledge Hub

Security Advisory: Local Privilege Escalation via the NetBird Client Daemon IPC

We fixed a local privilege escalation issue in the NetBird client daemon in v0.76.0. Here is what the issue was, who it affected, how we fixed it, and how we handle security reports.

On July 29, 2026, we released NetBird v0.76.0 together with GHSA-qcpp-8vwj-hhwr , a security advisory for a local privilege escalation issue in the NetBird client daemon. The issue was reported to us by an external security researcher, and it affected every NetBird client release from v0.5.0 (March 2022) through v0.75.1.

We are writing this post because a GitHub advisory answers "what was the CVSS score" but not "should I be worried, and what do I do on Monday morning". Below is the full picture: what the issue was, what an attacker could and could not do with it, what we changed, and how we handle reports like this one.

TL;DR

  • What: The NetBird client daemon accepted control commands from any local process with no authentication. An unprivileged local user could read network configuration, rewrite security-relevant daemon settings, disconnect the VPN, and move the peer to another NetBird account. Escalating that to a root shell additionally required SSH to be enabled for the peer on the management side — either because the account already allowed it, or because the attacker re-pointed the peer at a management server they controlled.
  • Severity: High, CVSS 3.1 score 8.8 ().
  • Affected: NetBird client versions on Linux, macOS, FreeBSD, and Windows.
  • Fixed in: v0.76.0.
  • Not affected: The NetBird management server, signal server, relays, and NetBird Cloud. This was never remotely exploitable and never crossed from one machine to another.
  • What to do: Upgrade clients to v0.76.0 or later. If you cannot upgrade immediately, see Mitigations below.

Why we are publishing this

NetBird is open source, and it sits in a privileged position on every machine it runs on. The daemon runs as root, manages the WireGuard interface, and writes firewall and SSH configuration. That is a lot of trust to ask for, and the only way to keep earning it is to be specific in public when we get something wrong.

We have written before about our response to a spear-phishing campaign that misused NetBird, and about third-party vulnerabilities like regreSSHion . This one is different: this was our own bug, in our own code, and it was there for over three years. Publishing the uncomfortable details is the same commitment as publishing the comfortable ones.

What the issue was

The NetBird client is split into two parts: a privileged daemon () that does the actual networking work, and unprivileged clients — the CLI and the desktop UI — that tell the daemon what to do. They talk to each other over a local gRPC control interface.

That interface had no authentication.

On Linux, macOS, and FreeBSD it was a Unix domain socket at created with mode , world-readable and world-writable. On Windows it was a loopback TCP listener on , a transport that carries no caller identity at all. In both cases, any local process running as any user could connect and invoke every daemon RPC without presenting credentials.

This was not an oversight in the sense of a missing check that someone forgot to write. It was a deliberate design choice that aged badly: making the socket world-writable is what allowed the desktop UI and the CLI to work without asking the user for on every action. The convenience was real. The security assumption underneath it — that any local user is as trustworthy as root — was not.

What an attacker could do

The attacker model here is a local, unprivileged account on a machine running the NetBird client: a low-privilege service account, or a shared workstation user. No NetBird account, no admin rights, no user interaction, and no network access were required.

From that position, an attacker could:

  1. Read NetBird network configuration — management server URL, WireGuard port, peer IP addresses, FQDNs, public keys, and SSH settings.
  2. Modify security-critical daemon settings, which the root-owned daemon then persisted to its own config file — including enabling the NetBird SSH server and enabling SSH root login.
  3. Disconnect the VPN and deregister the peer from the victim's NetBird account.
  4. Re-register the peer to an attacker-controlled NetBird account using a setup key.
  5. Obtain a root shell through the built-in NetBird SSH server, once root login was enabled. This step has additional prerequisites, described below.

Step 5 is why this scores 8.8 rather than something in the mid range. The chain turns "I have a local unprivileged shell" into "I am root", and it does so through a service the operating system trusts. That is a broken privilege boundary, which is what the (scope change) in the CVSS vector represents.

Step 4 deserves its own note: re-registering a peer to an attacker's account is the more interesting outcome for a long-game attacker, because it quietly moves a machine's network membership without touching anything a user would normally look at. It is also, as the next section explains, what makes step 5 reachable when your own account would otherwise have blocked it.

Local config alone was not enough for the root shell

This is worth being exact about, because "unprivileged local user gets root" reads as though flipping two settings in a local file is the whole attack. It is not. The NetBird SSH server is gated on both sides, and the tampered local config only satisfies one of them.

Two conditions have to hold before the SSH server will accept anything:

  • The local config must allow it — the SSH server enabled, and root login enabled for the shell to be root's. This is the part the IPC issue let an unprivileged user control.
  • The management server must also enable SSH for that peer. The client stops the SSH server whenever the network map it receives does not have SSH enabled, regardless of what the local config says ( ). A locally-enabled SSH server with SSH turned off in the account simply does not run — the client logs and stops.

On top of that, the SSH server will not accept a connection from the machine it runs on. It listens on the peer's NetBird address and its connection validator rejects connections originating from the peer's own NetBird IP — explicitly, to stop local applications from connecting to themselves — as well as any source outside the NetBird network range. So the attacker cannot simply after tampering with the config. The session has to arrive from another peer inside the NetBird network, which in turn requires an access policy permitting SSH to that peer.

That leaves two realistic paths to the root shell, and the difference between them matters:

  1. Your account already permits it. If SSH is enabled for that peer in your account and a policy allows SSH from another peer the attacker can reach or already controls, then enabling root login and turning off the client's SSH authentication requirement is the last missing piece, on a service that was already exposed and reachable. This is the scenario where your management configuration is doing real work as a control, and where it holding the line prevents the escalation.
  2. The attacker supplies their own management side. Because the IPC also allowed changing the management URL and re-registering the peer with a setup key (steps 3 and 4), an attacker could point the peer at a NetBird account or self-hosted management server they control, have that side enable SSH for the peer and permit SSH from their own peer, and then connect.

Path 2 is the one worth understanding properly, because it does not depend on any single local flag. The client takes its SSH authentication configuration — the JWT issuer, audiences, and key location — from the management server it is talking to. An attacker who has re-pointed the peer at infrastructure they control therefore supplies that configuration too, and can present a token their own issuer signed. Turning off SSH authentication locally was a shortcut, not a requirement; the same result was reachable by handing the client an attacker-controlled authentication config. That is why we treated this as a real privilege escalation rather than a hardening issue, and why the fix gates management URL changes and deregistration rather than only the SSH flags.

Path 1 is the one that matters for assessing your own historical exposure: if SSH was disabled for a peer in your NetBird account, and that peer stayed pointed at your management server, the local tampering by itself did not yield a root shell on that machine.

What was not affected

Being precise about the blast radius matters as much as being precise about the bug:

  • The NetBird management server, signal server, and relays were not affected. The issue was entirely in the client's local IPC.
  • NetBird Cloud infrastructure was not affected, and no NetBird-operated system was involved in exploitation.
  • This was not remotely exploitable. An attacker needed code execution on the machine already, as a local user.
  • It did not let an attacker move laterally by itself. Compromising one peer this way did not grant access to other peers beyond whatever your NetBird access policies already permitted for that peer.
  • Your NetBird account was not exposed. Deregistering or re-registering a peer affects that one machine's membership, not your account's other peers or its data.

We have no evidence of this issue being exploited in the wild.

How we fixed it

The fix ( PR #6967 ) stops treating "can reach the socket" as "is allowed to do anything". Instead, the daemon now asks the kernel who is on the other end of the connection, and gates the dangerous operations on the answer.

The daemon derives each caller's identity from the operating system, not from anything the caller sends:

PlatformMechanism
Linux on the Unix socket
macOS, FreeBSD on the Unix socket
WindowsThe named-pipe client token

A caller whose identity cannot be established is refused rather than assumed benign.

Root or administrator is now required to enable the SSH server, enable SSH root login, disable SSH authentication, or to change the management URL or deregister a peer while that profile has the SSH server enabled. Turning any of those protections off — that is, moving toward the safer state — or restating a value that is already set stays unprivileged, so ordinary use does not start demanding elevation.

Those two groups together close both paths to the root shell. Path 1 is closed because an unprivileged caller can no longer disable SSH authentication or enable root login on a peer where SSH is already permitted. Path 2 is closed because the attacker cannot reach a state where re-pointing the peer helps them: the management URL and deregistration are gated whenever the SSH server is enabled, and enabling it in the first place now requires root.

Windows moved from loopback TCP to a named pipe. When the daemon runs as a service, the pipe is created in the namespace, which only administrators can create in, and clients verify the pipe's owner before trusting a name that carries no such guarantee. A persisted daemon address is migrated to the pipe automatically, and if only the legacy address answers, the client reports that rather than silently using it.

Rootless and containerized setups keep working. A caller sharing the identity of an unprivileged daemon is treated as privileged relative to that daemon, so rootless containers and netstack mode are unaffected.

Refusals explain themselves. A denied operation comes back as a gRPC carrying a detail with a summary and the exact command that performs the same operation with the privileges it needs. The CLI prints that guidance instead of an opaque error, and the desktop UI rolls back the refused change, shows the reason with a copy button, and disables guarded controls with the elevated command inline.

What this release does and does not gate

This release is deliberately scoped to the operations that let a local user cross into root, and it is worth being clear that it is not a blanket lockdown of the IPC. Reading daemon status and configuration, and non-escalating operations such as disconnecting, remain available to local users — as do other daemon settings that do not lead to privilege escalation. Gating everything would break a large amount of legitimate unprivileged tooling for no security gain, so we drew the line at escalation.

What you should do

Upgrade the NetBird client to v0.76.0 or later on every machine. The management server needs no changes for this fix, and there is no configuration to turn on — the authorization is unconditional in v0.76.0.

You can check what a peer is running from the dashboard's peer list, or on the machine itself:

Then update using the same method you installed with — your package manager, the install script , MDM, or your container image tag.

If you were running an affected version

There is no reliable indicator of compromise for this issue, because a successful attack looks exactly like a legitimate configuration change. If a machine had untrusted local users or a process you now suspect, inspect its NetBird configuration for settings that should not be enabled — in particular , , and , plus an unexpected :

PlatformConfig location
Linux (older installs: )
FreeBSD
Windows

Because the root shell needed the management side too, the same check is worth running from your account: whether SSH was enabled for those peers, whether any policy permitted SSH to them, and whether each peer still belongs to the account you expect it to. Your account's audit events will show unexpected peer deregistrations or additions. A peer that was pointed at an unfamiliar is the signal that matters most — that is the path that bypasses your own SSH configuration entirely.

If you cannot upgrade yet

Upgrading is the fix; everything else is a stopgap. Until you can:

  • Restrict socket access. On Linux, macOS, and FreeBSD, tighten so that only root and a trusted group can reach it. This will break the desktop UI and unprivileged CLI use for everyone outside that group, which is precisely the trade-off the v0.76.0 fix removes.
  • Reduce the number of local accounts with shell or code-execution access on machines running the NetBird client. This bug was only ever reachable by a local principal.
  • Disable SSH for peers that do not need it, in your NetBird account, and keep SSH access policies tight. This closes path 1 above: with SSH disabled on the management side, local config tampering does not start an SSH server. It does not close path 2, since an attacker who re-points the peer at their own management server supplies that setting themselves — so treat this as narrowing the attack, not eliminating it.
  • Watch for management URL changes. A peer whose no longer points at your server, or that disappears from your account, is the observable side of path 2.

Timeline

DateEvent
2026-07-29Fix merged ( PR #6967 )
2026-07-29 v0.76.0 released
2026-07-29 GHSA-qcpp-8vwj-hhwr published

Credit

This issue was reported to us by neewek . Thank you for the clear write-up and the proof-of-concept, and for giving us the room to ship a proper fix rather than a patch over the symptom.

Reports like this are the most valuable thing an open-source security project receives. A researcher who takes the time to build a working chain and explain it is doing work we cannot fully replicate internally, no matter how good our review process is.

How we handle security reports

If you find a vulnerability in NetBird, email security@netbird.io or use GitHub's private vulnerability reporting on the netbirdio/netbird repository. Please do not open a public issue for a security finding.

What you can expect from us:

  • We acknowledge reports and work with you on severity and scope.
  • We fix, release, and then publish a GitHub Security Advisory naming the affected version range and the patched version.
  • We credit reporters who want to be credited.
  • We support the latest release, so fixes ship in the next version rather than as backports.

Our full policy lives in SECURITY.md , and every advisory we publish is listed on our GitHub Security Advisories page . You can subscribe to release notifications to hear about security releases as they happen.

Closing thought

The uncomfortable part of this issue is not the bug itself — it is that it survived for three years because the design choice behind it was convenient and never revisited. Local-privilege assumptions are easy to inherit and hard to notice, especially in a component that has always worked.

We have taken two things from this beyond the fix. First, the caller-identity check the daemon now performs is the kind of control that should be structural rather than per-endpoint, and it is now applied at the IPC layer for everything that matters. Second, "any local user is trusted" is an assumption worth auditing explicitly across the client, which is work we are continuing.

If you run NetBird, upgrade to v0.76.0 . If you find something else, tell us — we would much rather hear it from you than read it somewhere else.