Tailworm turns one hacked laptop into root on your Tailscale SSH servers. Upgrade to 1.104.4 (TS-2026-014)

5 min read

How to fix Tailworm (TS-2026-014): upgrade Tailscale to 1.104.4 on every node running Tailscale SSH or serve. A device already on your tailnet could use SSH socket forwarding to get root on servers its SSH rules reach. If you cannot upgrade yet, set TS_SSH_DISABLE_FORWARDING=1.

BREAKING: Tailworm turns one hacked laptop into root on your Tailscale SSH servers. Upgrade to 1.104.4 (TS-2026-014)
On this page

If one laptop or CI runner on your tailnet gets compromised, Tailworm lets it take root on every server that laptop is allowed to reach over Tailscale SSH. It does not need a password or a key. The attacker opens an ordinary SSH session and asks for socket forwarding, and the root tailscaled process does the rest. Cyberdione Labs published the findings, which it calls Tailworm, on Oct 25 before any fix existed. Tailscale tagged the fix, 1.104.4, on Tuesday Oct 27, and the GitHub release went up at 9:05 PM ET alongside security bulletins TS-2026-014 through TS-2026-017.

The bad part is the SSH forwarding bug (TS-2026-014). In the researcher's lab it chained machine to machine in under a second per hop with no user interaction. That was a lab run on unmodified 1.104.2 and headscale 0.29.5. Neither Tailscale nor the report mentions exploitation in the wild.

How to fix

Upgrade every node that runs Tailscale SSH or tailscale serve to 1.104.4 or newer:

sudo tailscale update          # Linux and other installs that support self-update
tailscale version              # first line must read 1.104.4 or higher

If your package mirror or image does not carry 1.104.4 yet, turn off SSH forwarding until it does. This is Tailscale's own interim fix. It leaves Tailscale SSH working and closes the forwarding paths behind the root escalation:

# Debian/Ubuntu/RHEL packages read /etc/default/tailscaled
echo 'TS_SSH_DISABLE_FORWARDING=1' | sudo tee -a /etc/default/tailscaled
sudo systemctl restart tailscaled

Then fix the policy, which the upgrade does not do for you. Rewrite SSH rules as specific source-to-destination pairs instead of "any member to any tagged server", and stop granting root or passwordless-sudo accounts as SSH targets. In the lab, per-pair rules alone stopped the spread. On Tailscale's hosted control plane you can also deny forwarding per SSH rule. On headscale 0.29.5 you cannot, so the report treats every SSH grant there as root on the target.

What 1.104.4 changes

Before the fix, Tailscale SSH checked forwarded Unix socket paths against an allowlist by name, then did the dial, bind or unlink as root. That gave three ways in, all fixed by TS-2026-014:

  • ssh -L to a Unix socket dialed it as root, so a normal session could talk to root-only sockets such as the Docker socket or the tailscaled local API.
  • ssh -R could unlink a root-owned socket and put the attacker's listener in its place.
  • A race between the path check and the use defeated the allowlist, because the kernel resolves the path again after the check.

The fix does not patch the allowlist. It removes root from the operation. The commits between v1.104.2 and v1.104.4 route forwarding through a per-connection monitor that hands each dial, bind and unlink to a child process running as the logged-in session user ("relay Unix forward payloads as the bound user", "dial TCP forwards as the bound user"). Kernel file permissions are now the boundary. OpenSSH has always worked this way, and so has Dropbear since its fix for a similar bug, CVE-2025-14282.

The same release carries three smaller fixes:

  • TS-2026-015: an authenticated SSH session could grow tailscaled's memory without limit through window-change messages and environment appends until the OOM killer took the daemon, and every Tailscale connection on that node with it.
  • TS-2026-016: tailscale serve --tcp pointed at a Unix socket dialed as root and could reach the tailscaled local API, which is full node takeover. The symlink guard for HTTP backends only ran on config reload, so a path could be swapped after the fact. Serve now refuses the daemon socket and rechecks on every dial.
  • TS-2026-017: with serve over TLS, HTTP/2 streams on one connection shared a context, so Tailscale-App-Capabilities meant for one mount could bleed into a request for another. That only matters if your app authorizes on that header and you have two mounts with different capability rules on one node.

Check your nodes

Find out whether a node is exposed before you schedule the restart:

tailscale version                         # below 1.104.4 = unpatched
tailscale debug prefs | grep -i runssh    # "RunSSH": true means Tailscale SSH is on
tailscale serve status                    # look for TCP forwards to unix: paths
systemctl show tailscaled -p Environment  # confirms TS_SSH_DISABLE_FORWARDING=1 took

Then open the ssh section of your tailnet policy file. Any rule whose src is a group or autogroup:member and whose dst is a tag covering many servers is the rule that lets one foothold become a fleet.

Check mode does not save you here. Tailscale SSH rules with "action": "check" make the user sign in again before a session starts, and many teams lean on that for production access. The report measured that the forwarding bugs run inside the already approved session, and on the hosted control plane one approval covers every check-mode destination for the whole check period (12 hours by default). Treat check-mode servers as exposed.

Who is not in scope

  • Nodes that do not run Tailscale SSH and do not use tailscale serve with Unix socket backends. Plain WireGuard mesh traffic between nodes is not part of this.
  • Servers you reach with regular OpenSSH over the tailnet rather than Tailscale SSH. OpenSSH drops to the session user before forwarding.
  • Tailnets with Tailnet Lock on. In the lab the chain still captured the enrollment secret but could not finish joining a rogue node. Root on the target was still reached.
  • Self-hosted control servers are a separate patch. The headscale memory exhaustion findings and the experimental app connector egress bug (only partly fixed, with an open PR) are not closed by 1.104.4. The report says its interim guidance still applies to headscale up to 0.29.6.

What this is not

This is not a remote break-in from the internet: the attacker must already control a device on your tailnet that an SSH rule lets reach the target. As Tailscale's VP of Engineering Mike Shaver put it, it "isn't the same as an unauthenticated attacker being able to connect to arbitrary machines." The catch is how ordinary that first device is. The report lists a vulnerable web app, a poisoned package or a phished login. A developer laptop running an AI coding agent that installs whatever a README suggests counts too. Once that laptop is on the tailnet with SSH rights, the old trust model is "anything it can reach, it owns."

No CVE IDs had been published for these findings at the time of writing. Tailscale's bulletin IDs are the identifiers to track. We will update this post if CVEs or exploitation reports appear.

References

All articles

Keep exploring

The engineering desk continues across the ecosystem: security research on shipping agents safely, and plugins that put the standards to work.