What is the Tailworm security assessment?
On October 25, 2026, security researcher Erica Windisch of Cyberdione Labs published Tailworm, an assessment of Tailscale v1.104.2, headscale v0.29.5, and related components such as the gliderssh library. The report has eleven findings covering privileged Unix socket operations, application-connector routing, local API exposure, HTTP/2 authorization behavior, and denial-of-service conditions.
Most vulnerability disclosures deal with one buffer overflow or one cryptographic flaw. Tailworm goes after something broader: how a zero-trust mesh turns network-level authorization into operating-system-level privilege. Its central question is whether a networking agent running as root can safely perform IPC operations requested by remote users when path validation, peer credential checks, and OS privilege separation do not enforce the same boundary.
That matters because Tailscale and its open-source control plane alternative, headscale, are built to reduce network exposure. Devices talk over encrypted tunnels under centrally managed access policies. Tailworm shows that this does not settle what happens after a connection is authorized. A device can be authenticated, the tunnel can work perfectly, and the policy can allow the connection, while a vulnerable privileged process still lets that connection cross a local kernel boundary it was never meant to cross.
One caveat before going further. Tailworm is a researcher-published assessment, not a vendor advisory, so each exploitability claim should be kept separate from findings Tailscale has independently confirmed. Earlier security bulletins on Unix socket forwarding and Tailscale Serve do show that these vulnerability classes are real, ongoing engineering problems for mesh networking.
Why Unix domain sockets are risky for a privileged networking agent
A Linux host enforces several boundaries in the kernel: process credentials, filesystem permissions, discretionary access controls (DAC), mandatory access controls (MAC) such as SELinux or AppArmor, and network namespace restrictions. Services running under different user IDs can talk to each other, but the OS uses those identities to limit what each one may do.
Unix domain sockets (UDS) complicate this once a mesh network is involved. TCP sockets use the network stack and identify peers by IP address and port. Unix domain sockets provide local IPC through the filesystem namespace or an abstract socket namespace. Privileged services use them to expose administrative interfaces without opening a network port. Docker, systemd, and container runtimes all do this, and the Docker socket at /var/run/docker.sock is the standard example of an endpoint that gives extensive, often root-equivalent control of the host.
The security of such a socket depends on who can open it and how the server identifies its client. On Linux, a server can read the kernel-reported UID, GID, and PID of the connecting process using SO_PEERCRED, which is far more reliable than a username supplied inside the application protocol.
The danger appears when a root-owned networking agent opens connections to those sockets for remote users. The remote user does not need the filesystem permissions that would normally allow access. The agent makes the connection, and unless it drops privileges or constrains the operation through file descriptor passing, the local service sees the agent’s root credentials instead of the requesting user’s. This is a textbook confused-deputy problem: a privileged component uses its own authority on behalf of a less-privileged requester and bypasses the local access controls.
How Tailscale SSH differs from OpenSSH
In a conventional OpenSSH deployment, the client authenticates with a public key, password, or certificate. The server then applies its SSH configuration and OS account restrictions before starting the session. OpenSSH also uses strict privilege separation: a privileged monitor process handles network I/O and key exchange, and an unprivileged child process handles the authenticated user session.
Tailscale SSH ties the connection to Tailscale’s device identity and access-control system instead. The WireGuard-based mesh establishes the cryptographic identity of the connecting node. Tailscale SSH then checks the applicable SSH policy through the coordination server to decide whether that node can reach the destination and which OS accounts it may use. In the implementation Tailworm examines, SSH authentication often completes with the none method because the mesh tunnel has already authenticated the peer. That reflects the integrated design and does not mean the connection is unauthenticated.
📬 Stay Ahead of Cyber Threats
Get the latest cybersecurity news, critical vulnerabilities, threat intelligence, tutorials, and exclusive giveaways delivered straight to your inbox. No spam. Unsubscribe anytime.
Subscribe to the Newsletter →The tailscaled daemon runs as root to manage network interfaces, routing, and WireGuard tunnels. It also acts as the SSH server broker, using gliderssh, a Tailscale-specific fork of the gliderlabs/ssh Go library, to handle the SSH protocol.
The security question is what the mesh-level authorization decision covers locally. An SSH policy can let a source device reach a destination and use particular accounts. Authorization to open a session does not guarantee that every operation inside it, especially IPC forwarding, runs with the intended local privileges. If an attacker compromises an ordinary account on a device that is allowed to start Tailscale SSH connections to a production server, malware can inherit that ability without stealing a separate SSH private key, because the device identity and existing policy are enough. When forwarding is permitted, the remote user can request an operation that the root tailscaled process performs, which may cross a local privilege boundary even though the session itself was authorized correctly under the tailnet’s ACLs.
Tailworm finding: Unix socket forwarding performed as root
The first group of findings covers local and remote forwarding through Unix domain sockets. SSH local forwarding lets a client reach a service through a connection opened by the remote server. Remote forwarding lets a client arrange for a listening endpoint on the remote side that sends traffic back through the SSH connection. Unix socket forwarding applies the same idea to local IPC, so the implementation opens or creates a Unix socket at a filesystem path instead of connecting to a TCP endpoint.
Tailworm reports that the Tailscale SSH server in tailscaled v1.104.2 implements this forwarding through gliderssh. A remote peer whose policy permits the relevant forwarding operation can make the root-privileged agent connect to a protected Unix socket.
Dialing the Docker socket
The report uses the Docker socket as its example target. Its administrative interface lets a client create containers, mount host directories, and reach resources an ordinary user cannot. If forwarding connects to that socket as root, the Docker daemon sees a process that already has the needed filesystem permissions. The remote account does not need to be in the local docker group or have sudo. The agent becomes an unrestricted bridge into privileged local IPC endpoints. An attacker controlling the forwarding client can then send Docker API requests through the channel and potentially get host-level command execution by creating a container that mounts the host filesystem.
BindUnlink: replacing sockets through remote forwarding
The second finding concerns remote forwarding. A remote-forwarding request can make the privileged agent unlink an existing socket and create a new listener at the requested path. The report calls this behavior BindUnlink.
The impact is worst when the path points to a socket owned by root or used by a privileged service. Replacing the socket redirects later clients to an endpoint the attacker controls. If a privileged service normally listens on a well-known Unix socket and another local process connects to it assuming the daemon is on the other end, an attacker who has swapped in a forwarding listener can intercept command-oriented control interfaces, capture credential-bearing API tokens, or manipulate Docker API connections to expose image and container data. This undermines the assumption that a well-known socket path identifies the intended service, and it turns the local IPC namespace into an active attack surface.
TOCTOU race conditions in socket path validation
The mechanism behind these unauthorized connections and replacements is the Time-of-Check-to-Time-of-Use (TOCTOU) race. A program checks a resource, and an attacker changes what that resource refers to before the program uses it. Filesystem paths cause these bugs constantly. A pathname is a string used to look up an object, not a permanent reference to it, so it can resolve differently as directory entries and symlinks change between validation and execution.
Tailworm argues that the socket-forwarding checks in the Tailscale implementation validate requested paths with functions such as validateAndResolveListenPath and validateAndResolveSocketPath before the socket operation happens. The gap between check and use lets an unprivileged process change a directory entry or symlink after the check passes but before the privileged process opens, binds, unlinks, or modifies the path. A check based on the textual path or on stat() can approve a safe-looking name while the root-privileged operation reaches a different target, such as a sensitive system socket or a protected configuration file.
The report describes lab testing that showed consequences on both the bind side and the dial side. These include replacing a root-owned control socket and making unauthorized connections to privileged Unix sockets. It also describes a related unlink issue where the type check runs on one object while the unlink resolves through a changed symlink, which could remove an unintended file.
Why allowlists and canonicalization are not enough
A lexical allowlist that compares the requested path to approved names or prefixes, such as a home directory or /tmp, is not enough. Symlinks, directory renames, and mount namespace changes can alter which object a path reaches. Canonicalizing the path first does not remove every race either, because the filesystem can change between canonicalization and the syscall.
Linux offers openat2() with restrictions such as RESOLVE_BENEATH and RESOLVE_NO_SYMLINKS to reduce TOCTOU races when opening files. These are hard to apply to the bind() and connect() syscalls used for Unix domain sockets, which work on pathnames and not on pre-opened file descriptors.
The same class of bug keeps appearing in other software. CVE-2026-65183 in Apache Tomcat is a recent example: race conditions when creating Unix domain sockets let unauthorized local users reach privileged endpoints. A correct design for Unix socket operations has to protect the surrounding directory from untrusted modification, use dedicated directories with strict ownership and permissions, and prevent unsafe symlink traversal. No single path-validation helper can make up for an architecture that gives remote requests unnecessary root authority over the local IPC namespace.
Why Go’s net package makes this harder
Go’s net.Dial("unix", path) and net.Listen("unix", path) end up calling the POSIX connect() and bind() system calls. File operations in Go can use os.OpenFile with specific flags or openat2 wrappers. Socket operations are tied to the string form of the path at the moment the kernel executes the syscall.
A Go networking daemon that validates a path with os.Stat() or os.Lstat() before calling net.Dial() or net.Listen() is therefore exposed to TOCTOU races. The kernel’s VFS layer resolves the path again during the syscall and may follow a symlink that was swapped in the meantime. gliderssh, as the intermediary for Tailscale SSH, inherits these OS-level problems. It needs syscall-level mitigations beyond standard library path validation so that the destination of a forwarded connection stays fixed between the authorization check and the actual socket creation or connection.
From socket hijacking to the Tailscale local API and Tailnet Lock
The effects go beyond individual local services. They reach the control plane of the mesh through the Tailscale local API and tailscale serve. Tailworm describes an extended exploit chain in which a successful socket replacement or unauthorized local forwarding operation lets an attacker intercept traffic meant for the Tailscale local API, which normally sits behind a Unix socket and handles local administrative commands and status queries.
The report claims that an enrollment authentication key could be captured from a request crossing the compromised channel and used to register a rogue node in the tailnet, subject to the key’s permissions and lifecycle limits. It also covers Tailnet Lock, the mechanism that controls which nodes can join a tailnet by requiring explicit approval or specific cryptographic signatures. According to the assessment, sensitive operations and enrollment material can be intercepted or abused if the local API socket is exposed to remote forwarding requests.
The local API communicates over a Unix socket using HTTP/2 or a similar multiplexed protocol. An attacker who intercepts it gets structured API requests that may contain state changes or Tailnet Lock signatures, not just raw bytes. If the API trusts every connection from the local socket, or relies on SO_PEERCRED, which would wrongly report uid=0 because of the root-owned proxy, the authorization layer is bypassed.
Tailscale has dealt with similar boundary problems before. Security bulletins TS-2026-004 and TS-2026-005 covered two cases: Tailscale Serve Unix socket proxy targets that were not restricted to the root user, which let a non-root operator proxy privileged sockets, and SSH Unix socket forwarding that did not respect symlink permissions. Mapping a decentralized, identity-aware mesh onto the permission model of a Unix-like OS is hard, and these issues keep coming back.
An attacker who controls a privileged agent’s local control channel can alter device identity, intercept enrollment credentials, and undermine the cryptographic trust anchors of the whole tailnet. This changes incident response. Removing the exploit path or reinstalling the host does not invalidate credentials or keys that were already copied to an attacker-controlled node. Operators have to find and revoke the affected credentials through the central control plane, because host-level cleanup alone will not do it.
Peer enumeration and silent reachability probing
Tailworm also looks at information disclosure in the mesh topology. It describes unconditional peer enumeration and silent reachability probing as factors that could lower the barrier to exploitation.
The report argues that a node can learn about other tailnet peers, including device names, IP addresses, and metadata tags, without that information being limited to the devices its ACLs allow it to reach. It also describes differences in connection behavior, such as particular timeout signatures or TCP reset patterns, that could help an observer tell unreachable or filtered peers from ones that respond. That would allow quiet port scanning and topology mapping across the encrypted mesh.
These claims need careful reading. Knowing a device exists on the control plane does not give the cryptographic permission to open a WireGuard tunnel or an application-layer connection to it. A timeout does not prove a policy dropped the packet either, since routing problems, host firewalls, unavailable services, and transient network conditions can look identical.
Even so, broad peer visibility gives an attacker with one compromised device an accurate map of the environment. It makes high-value targets easier to find and lateral movement easier to plan. Network membership, device discovery, and service authorization are separate security questions. A mesh can reasonably give devices routing information about other members so they can connect peer to peer, but operators should know what metadata reaches the local client and should not assume an encrypted network hides its internal topology from compromised members. Identity providers and device posture checks feed the control plane, so the network directory is often replicated to edge devices. That creates a tension between easy peer-to-peer connection setup and least-privilege topology visibility.
Precedent: Dropbear CVE-2025-14282 and OpenSSH privilege separation
The issues Tailworm raises have precedent, and the history points to a fix. In late 2025 and early 2026, the Dropbear SSH server disclosed a privilege-escalation vulnerability tracked as CVE-2025-14282. It affected versions 2024.84 through 2025.88 and involved Unix stream-socket forwarding performed with root privileges.
As in the Tailworm scenario, an authenticated user on a multi-user Dropbear system could reach protected Unix sockets through the SSH server. Filesystem restrictions and peer-credential checks were bypassed because the root-owned daemon performed the forwarding, not the user’s session process. Dropbear fixed this in version 2025.89 by changing its process architecture to drop privileges right after authentication, and by restricting Unix socket forwarding in certain forced-command configurations. The process handling the forwarded connection then ran with the same filesystem permissions as the authenticated user.
OpenSSH has long worked this way. A privileged monitor process handles network I/O and key exchange, and an unprivileged child handles the authenticated session and any forwarding requests, so the root process never acts as a direct proxy for user-initiated IPC.
OpenSSH, Dropbear, and the architecture Tailworm examines have different authentication models, languages, and design goals, so the comparison should not be pushed too far. They do share one lesson: process privileges are a security control, not an implementation detail.
How to fix privileged forwarding: drop privileges after authentication
Developers should not rely only on path filtering or allowlists when a process can avoid the privilege mismatch by dropping privileges. If forwarding runs as the authenticated session user, the OS’s own access controls refuse sockets that user cannot reach. The server then no longer has to anticipate every sensitive pathname, symlink arrangement, or future socket location.
This does not make every forwarding implementation safe. A user with broad privileges can still do damage, and a service can still expose an insecure interface. It does remove an unnecessary source of authority from the request-handling path. The most durable fix for a privileged forwarding bug is usually to remove the unneeded privilege from the forwarding operation itself, not to add another layer of pathname validation or ACLs on top of a flawed execution model.
What mesh networking operators should take from Tailworm
As Tailscale and headscale replace bastion hosts and VPN concentrators in more environments, zero-trust network access has to be squared with OS-level privilege boundaries. Encrypting the transport and centralizing identity does not excuse the local daemon from privilege separation.
For privileged networking agents, the Tailworm findings point to several changes:
- Drop privileges after authentication, so the process that dials local Unix sockets or binds filesystem paths runs with the authenticated user’s credentials.
- Use kernel APIs such as
openat2()and secure file descriptor passing for resources that really need elevated privileges. - Treat the local API and application connectors as separate security domains that require explicit, verified consent, not trust inherited from a valid mesh tunnel.
- Keep the authority granted to a network proxy within the authority of the user it represents.
Device identity and network membership give a solid baseline of trust, but they cannot override the user and group separation the operating system provides.









