TAILWORM Inside the Tailscale SSH Privilege Boundary
Flaws in Tailscale SSH and Serve can cross local privilege boundaries, expose node-local services to tailnet peers, or exhaust resources. SSH paths require policy-authorized access; Serve paths have separate local-access prerequisites. Tailscale published four security bulletins; their reported findings were fixed in v1.104.4.
Tailscale SSH is popular because it removes key management from SSH. The tradeoff is architectural: a root-privileged daemon, tailscaled, performs session and forwarding operations on behalf of a user whose device has already been authenticated by the mesh. When the daemon keeps its own privileges for operations that should run as the session user, the SSH policy stops being the only boundary that matters. That is the core of the Tailworm findings.
Executive Summary
The confirmed issues involve privilege escalation, denial of service, and an application-level authorization flaw; they do not bypass Tailscale device identity or SSH-policy checks. Exploitation of the SSH findings requires an attacker to already control an authorized device and to have SSH access permitted by policy. Tailscale says an unauthenticated attacker cannot connect to arbitrary Tailscale SSH servers. It also disputes the report’s “worm” framing: the measured spread is lateral movement bounded by the tailnet policy file, not self-replicating malware.
The practical risk is still serious. An authorized Tailscale SSH session with forwarding permitted can cause the root tailscaled process to perform privileged Unix-socket operations, including reaching the Docker socket and writing to the local API. Tailscale’s TS-2026-016 describes a clean route to full node takeover through tailscale serve. TS-2026-014 removes the path allowlist as a security boundary; TS-2026-015 adds an authenticated memory-exhaustion denial of service; TS-2026-017 can bleed capability headers between HTTP/2 mounts.
Tailscale published four security bulletins (TS-2026-014 through TS-2026-017), all fixed in v1.104.4 or later. The Tailworm report also covers pre-production Conn25, self-hosted headscale, DERP client behavior, and Serve/Funnel design questions. Those are useful research leads, but they should not be blended into the confirmed Tailscale SSH exposure without labels.
Lab differentials: TS-2026-014, TS-2026-015, and TS-2026-016; TS-2026-017 was not tested, and the full reported chain was not reproduced.
Vulnerability at a Glance
| Field | Value |
|---|---|
| Report and advisories | Tailworm, Cyberdione Labs; Tailscale TS-2026-014 through TS-2026-017 |
| Severity | High for TS-2026-014, TS-2026-015, and the TS-2026-016 local API path; Medium for the contained Serve symlink race; Medium for TS-2026-017 with header-based authorization, Low–Medium otherwise (researcher ratings; no vendor CVSS). |
| Affected / fixed | Tailworm assessed Tailscale v1.104.2; Tailscale fixed TS-2026-014 through TS-2026-017 in v1.104.4. Exposure depends on the feature and prerequisites described below. |
| Components | Tailscale SSH in tailscaled; tailscale serve; local API |
| Prerequisite | Varies by bulletin; see the condition summary after the confirmed-findings table |
| Impact | Root-only socket access, node takeover via the local API, authenticated memory exhaustion, cross-mount capability bleed |
How Enterprises Should Respond
1. Patch first
Upgrade Tailscale clients to v1.104.4 or later. If SSH forwarding is not required, disable it now with TS_SSH_DISABLE_FORWARDING=1; then re-enable only where a documented need exists. Audit Serve configurations for TCP forwards to Unix sockets and for backend paths that can be changed by local users.
2. Scope SSH access
Use per-pair grants: source device or user to a specific target and account. Avoid identity-wide or tag-wide rules and privileged target accounts. Tailworm reports that headscale v0.29.5 grants forwarding unconditionally on accepted SSH sessions; treat those grants as root-equivalent until the headscale-side issue is fixed. Check mode adds an approval step for SSH access; it does not restrict forwarding.
3. Protect device identity
Use node-key expiry on server fleets, bind agent state to hardware where possible, and keep security telemetry on infrastructure a compromised host cannot reach. A root attacker can copy a node’s state file; reimaging the host does not revoke the stolen identity. Incident response must delete the device at the control plane, then rotate authkeys and any credentials reachable from the host.
4. Reduce runtime exposure
Run Tailscale where it needs to run, not on every workload by default. Restrict the sockets and local APIs reachable from a Tailscale-enabled host, and apply memory limits and restart supervision to tailscaled and headscale. For Conn25, if it is used in a test environment, enforce a destination allowlist or a default-deny egress policy on the connector namespace.
What Actually Broke
Tailscale SSH completes the SSH authentication phase with method none. The WireGuard layer has already identified the device, and the SSH policy evaluates that device identity against the target accounts named in the rule. There is no separate password or user key at the SSH layer. That design is the feature: access follows device identity and policy, not SSH credentials.
The failure is in what happens after authorization. tailscaled runs as root. Its embedded SSH server implements local (ssh -L) and remote (ssh -R) Unix-socket forwarding. The server validated socket paths lexically, then performed the privileged dial, bind, or unlink later as root; Tailworm additionally reports a chmod side effect. The kernel re-resolves paths at use time. A path that was safe at check time could be replaced before use, and a path restricted to root could be reached by the root daemon on behalf of an unprivileged session.
That is a confused-deputy problem. The session user should not inherit the root daemon’s ability to reach privileged services such as the Docker socket or the local API. When the daemon treats the session’s forwarding request as its own, it crosses that privilege boundary even though SSH authorization passed. Tailscale’s fix moves Unix-socket forwarding to the authenticated user’s context, so forwarded operations run with the session’s privileges rather than the daemon’s.
Related boundary failures also affected tailscale serve. A Serve TCP forward to a Unix socket bypassed a guard that existed on the HTTP path, passing raw bytes into the local API with the daemon’s peer credentials. A separate Serve guard checked a symlink only at configuration time, not on each dial. A different HTTP/2 race let capabilities from one mount leak into requests for another.
Four Tailscale Security Bulletins
| Advisory | What broke | Why it matters |
| TS-2026-014 | Unix-socket forwarding ran as root after lexical path checks. Local forwarding reached root-only sockets; remote forwarding could unlink and replace root-owned sockets; a TOCTOU race defeated the {home, /tmp, /run/user} allowlist. | An authenticated peer with forwarding permission could reach the Docker socket, the local API, or other root-only services. Tailscale calls the path allowlist no longer a security boundary. |
| TS-2026-015 | Session handling allowed unbounded memory growth through repeated window-change messages and environment appends; allocations persisted after disconnect. | An authenticated SSH peer could OOM-kill tailscaled, disrupting every Tailscale connection on the node. |
| TS-2026-016 | tailscale serve –tcp to a Unix socket lacked the guard used by the HTTP proxy, dialing the target as root. The HTTP symlink guard also ran only at config reload. | Raw bytes reached the local API, enabling node takeover; the symlink variant allowed root-context dials of arbitrary root-only sockets. |
| TS-2026-017 | A single serveHTTPContext was shared by all streams on an HTTP/2 Serve connection while capabilities were written per request and read in a shared hook. | A peer holding a capability for one mount could receive privileged responses from another mount when the application authorizes on Tailscale-App-Capabilities headers. The race is probabilistic but retryable for a patient peer. |
The fix ships in v1.104.4 or later. If you can’t upgrade immediately, set TS_SSH_DISABLE_FORWARDING=1 in the tailscaled service environment and restart; this disables forwarding, not SSH.
Conditions: TS-2026-014 requires policy-authorized SSH forwarding; TS-2026-015 requires a policy-authorized SSH session. TS-2026-016 requires local access to write Serve configuration or local-user write access to a configured backend path. TS-2026-017 requires TLS-enabled Serve, two mounts with different capability needs, a capability for one mount, concurrent streams on one HTTP/2 connection, and an application authorizing from the Tailscale-App-Capabilities header.
Lab Differential
The isolated Docker comparison used Tailscale v1.104.2 and v1.104.4. It produced version-dependent outcomes for TS-2026-014, TS-2026-015, and TS-2026-016. The TS-2026-014 and TS-2026-016 tests used a target container’s local API; the TS-2026-015 test measured session-memory growth. The lab did not reproduce a root shell, host local API compromise, or the full Tailworm chain.
TS-2026-014: SSH forwarding to the target local API
On v1.104.2, alice could read the target container’s local API directly (HTTP 200), but a direct write was denied (HTTP 403). Through Tailscale SSH local forwarding, the same client changed the target’s Hostname preference (HTTP 200), then restored its original value. On v1.104.4, the forwarded write returned HTTP 403 and the Hostname remained unchanged. This is a controlled local API write differential inside the target container, not a host local API compromise, arbitrary root execution, or a root shell.

Figure 1. Forwarding changes and restores the container local API on v1.104.2; v1.104.4 denies the write.
TS-2026-015: SSH session-memory growth
In the paired 110-second TS-2026-015 capture, each target received 1,000 SSH channels, 500,000 environment-variable requests, and 50,000 window-change requests. Both outputs report no client errors. The vulnerable v1.104.2 run shows an elapsed total of 121.4 seconds and post-run tailscaled RSS of 477 MB; the fixed v1.104.4 run shows 119.794 seconds and 173 MB. This pair shows higher memory use on v1.104.2; it is not an OOM-kill demonstration or a repeatability estimate.

Figure 2. Paired TS-2026-015 output shows elapsed time and post-run RSS for both builds.
TS-2026-016: Serve-to-local API differential
On v1.104.2, tailscale serve –tcp accepted a Unix-socket backend targeting the local API in the target container, and a tailnet peer received HTTP 200 from its status endpoint. This configuration and response were recorded three times. On v1.104.4, the same configuration was rejected, no listener appeared, and the peer could not connect in all three attempts. The target was a container, not the host local API.

Figure 3. Serve reaches the target container local API on v1.104.2; v1.104.4 rejects the configuration.
What the Broader Report Adds and What Tailscale Disputes
The Tailworm report is broader than the four bulletins. It describes a researcher-reported chain from an SSH session to root execution, enrollment-key capture, and a rogue node joining the tailnet. It also examines Conn25, headscale, the DERP client, and Serve/Funnel. Tailscale confirmed four client and Serve issues; it describes Conn25 as pre-production and says two Serve/Funnel behaviors work as designed.
Wormability. Tailworm reports that automated propagation in its lab reached every policy-permitted device without passwords or stolen keys. Tailscale disputes the “worm” framing: access remains bounded by tailnet policy and the activity is not self-replicating. Automated lateral movement can spread quickly through permissive grants without meeting the classic worm definition. Use per-pair rather than identity-wide or tag-wide SSH accept rules.
Conn25. Tailworm reports arbitrary egress selection through the experimental app connector. Tailscale says Conn25 remains pre-production and unreleased; stable app connectors are not affected. Track it as a design risk, not a current fleet exposure.
headscale. Tailworm separates two server-side availability findings in headscale v0.29.5: embedded-DERP sendHistory memory growth requiring an authenticated insider, and pre-authentication noise-auth-cache eviction that can block new-device onboarding. It separately reports a client-side DERP response-size issue that can exhaust client memory when it receives a hostile relay response. These are distinct components and prerequisites. Tailscale’s bulletins do not cover headscale; Tailworm’s matrix still lists the headscale-side work as pending; headscale v0.29.6’s changelog does not identify these fixes, so verify current status.
Serve/Funnel. Tailscale says five Serve/Funnel issues were reported: three bugs fixed in v1.104.4 and two behaviors that work as designed. The Funnel attribute bypass requires local-admin access. TS_SERVE_ALLOW_ALL_INTERFACES is opt-in and exposes Serve beyond the tailnet; enable it only when that exposure is intended.
Attack Scenarios
A compromised laptop, build worker, or plugin host already authorized on the tailnet needs no new password or SSH key: the attacker inherits its policy rights and can pivot to reachable SSH targets. If forwarding is allowed, the session can attempt to reach a target’s Docker socket or local API through the root daemon.
A local operator with Serve configuration access can create a TCP forward to a Unix socket; a backend path writable by local users also creates a race opportunity. Either can expose the local API or another root-only service, so Serve configuration is security-sensitive.
Detection
Monitor unlink, bind, and chmod events in forwarding-allowed paths such as home directories, /tmp, /run/user, and agent socket directories. Tailworm recommends object-scoped audit rules; a socket at mode 0755 where 0600 is expected may indicate a race-won bind.
Alert when one endpoint begins serving a second node identity, or when a machine key appears from a new network. Monitor authkey issuance/use outside change windows and tagged-node joins. For headscale, track RSS, cache churn, and registration latency.
Closing: Device Trust Is Not Session Privilege
Patch clients to v1.104.4 or later, disable unnecessary forwarding, and narrow SSH grants. Treat Serve configuration as security-sensitive, remove compromised node identities at the control plane, and rotate authkeys. Assess Conn25 and headscale separately. Device trust does not guarantee session privilege isolation.