Last weekend a security researcher published a report titled "Tailworm," describing vulnerabilities in Tailscale SSH and Tailscale Serve. This morning (October 28) Tailscale published its findings post and four security bulletins, TS-2026-014 through TS-2026-017, with fixes in v1.104.4. I read all four bulletins. They describe four different bugs, but three of them are the same bug wearing different clothes: tailscaled runs as root, it checked permissions as if it were you, and then it acted as root.
The timeline, because it matters
- Friday, October 23 — concerns about Tailscale SSH start circulating on social media; Tailscale begins investigating with external specialists.
- Saturday, October 24, morning — the researcher sends Tailscale the report, and publishes it two hours later.
- Monday, October 26 — Tailscale posts an investigation update with interim mitigations.
- October 28 — findings post (by Mike Shaver) and the four bulletins land; fixes ship in v1.104.4. I verified the release exists: GitHub shows v1.104.4 published today at 01:05 UTC, one step above v1.104.2 from October 21.
Report received Saturday morning, coordinated-ish disclosure in public by Saturday lunch, patched release the following Wednesday. You can argue about the two-hour gap; the response itself was fast.
TS-2026-014: forwarding, performed as root
The worst of the four. Tailscale SSH lets clients forward TCP and Unix sockets. The SSH server lives inside tailscaled, which runs as root. It validated Unix socket paths lexically — it looked at the path string — and then performed the operation as root. The bulletin lists three consequences:
-
Privileged dials via local forwarding (
ssh -L): the daemon dialed sockets your local user could never touch. -
Socket replacement via remote forwarding (
ssh -R): it would unlink root-owned sockets to bind yours in their place. - A race that defeated the allowlist: the kernel re-resolves the path after the check, so the path that was validated is not necessarily the path that got used.
Combined result: the path allowlist stopped being a security boundary, and an authenticated peer with forwarding permission had a route to root-only sockets — the bulletin names the two that matter, the Docker socket and the tailscaled local API socket. Either one is effectively the machine.
The fix is the correct one, and it's architectural rather than a patch to the check: Unix socket forwarding is now performed on behalf of the authenticated user, not as root. Don't validate harder. Drop the privilege.
TS-2026-016: same lesson, second classroom
tailscale serve can proxy tailnet connections to local Unix socket backends. Two weaknesses:
- TCP forwards to Unix sockets had no guard and dialed the target as root — which meant raw bytes could be passed straight into the local API socket using the daemon's own permissions. The bulletin calls the impact plainly: full node takeover.
- The symlink guard for HTTP proxies ran at configuration reload, not per dial. Replace a benign backend path with a symlink after reload and the guard never sees it.
Notice the shape: that's TS-2026-014's third bug again — a check performed at one time, a use at another, and an attacker who controls what changes in between. The fix mirrors it too: restrict TCP forwards to the tailscaled socket and revalidate the backend target on every dial.
The other two
TS-2026-015 is the unglamorous one that takes your network down. An authenticated peer could send continuous window-change messages and environment-variable appends inside a session, causing unbounded memory growth in tailscaled — and the allocations persisted after the peer disconnected. Keep going and the OOM killer takes out tailscaled, which is not just the SSH server; it's the node's whole networking state. Every Tailscale connection on that node dies with it. Fix: session memory is now bounded and released on disconnect.
TS-2026-017 is a concurrency bug in Serve over HTTP/2. All streams on one connection shared a single serveHTTPContext; the handler wrote AppCapabilities per request while the reverse proxy read them in a shared hook. Concurrent multiplexed streams could race, and capabilities from one mount could bleed into a request for another. If your app authorizes peers by inspecting Tailscale-App-Capabilities — which is the documented way to do it — it could make that decision with another application's capabilities. Fix: capabilities are now derived from a per-request context. Shared mutable state across multiplexed streams: an old bug class, in a new protocol's clothing.
The original report also had five findings in Serve and Funnel: three were bugs and are fixed in 1.104.4; two were behavior working as designed. A sixth, in the next-generation "Conn25" app connectors, was already known to Tailscale and is one reason Conn25 hasn't shipped; the stable connectors aren't affected.
What this is not
Tailscale is explicit, and the bulletins back it up: none of this is reachable by an unauthenticated attacker. Exploitation requires a foothold on an authorized peer in your tailnet, with SSH access already permitted by your policy file, plus the specific conditions (forwarding allowed, Serve with Unix backends, TLS Serve with multiple mounts). "Tailworm" is not a worm — there's no self-replication; it's lateral movement along connections your policy already allows, with the privilege boundary inside the destination machine drawn wrong. That's still serious — the whole point of Tailscale SSH's per-user mapping is that a peer allowed in as deploy is not allowed in as root — but the blast radius starts at "someone you already let in," not "the internet."
What to do
- Upgrade to v1.104.4 or later. All four bulletins close with the same sentence, and the release is live — I checked.
- If you can't upgrade today and you use Tailscale SSH, the interim mitigation from the investigation update still works: set
TS_SSH_DISABLE_FORWARDING=1in thetailscaledservice environment and restart. That kills forwarding (and with it, TS-2026-014's route) without disabling Tailscale SSH entirely. - Review the SSH section of your policy file while you're there. Every one of these bugs needed a policy-permitted connection to stand on; the smaller that set is, the less any of this mattered.
The pattern worth keeping: in three of the four bulletins, the check was fine and the ambient authority was the bug — a root daemon doing user-scoped work, validating carefully and then acting with privileges the user never had. Tailscale's fixes don't add more validation. They remove the privilege from the operation: forward as the user, revalidate at dial time, keep capabilities per-request. That's the right instinct, and it's portable to every privileged helper you've ever written.
Sources: Tailscale, "Tailscale SSH: Findings and fixes" (Oct 28, 2026) and security bulletins TS-2026-014, TS-2026-015, TS-2026-016, TS-2026-017 (tailscale.com/security-bulletins); Tailscale SSH investigation update (updated Oct 27, 2026); release timestamps from github.com/tailscale/tailscale/releases.
Top comments (0)