Skip to content

Back to the catalog

tailworm.comwriteup

Tailworm – Tailscale Vulnerabilities

Tailworm is a security assessment report by Erica Windisch (Cyberdione Labs) on Tailscale v1.104.2 and headscale v0.29.5, tested 22-24 October 2026. It documents 11 findings: Tailscale SSH unix-socket forwarding that runs as root (bind, dial and TOCTOU races), conn25 app-connector egress bypass, gliderssh memory exhaustion, serve/funnel flaws and headscale DoS. It includes PoC references, mitigations and fix status as of 27 October 2026. The page is truncated, so the later sections were not read.

TL;DR Audit report showing Tailscale SSH forwarding runs as root, enabling privilege escalation and worm-like spread, plus conn25, serve and headscale flaws.
Document

What it is

Tailworm is a security assessment by Erica Windisch (Cyberdione Labs), with 11 findings on Tailscale v1.104.2 and headscale v0.29.5, tested 22 to 24 October 2026. Windisch says they are unaffiliated with Tailscale Inc. and were not paid. The report was updated 27 October 2026. Fixes are in the public main branches of tailscale/tailscale and tailscale/gliderssh, but none had shipped in a numbered release. The interim mitigations in the report remain the operative guidance for tailscale ≤ v1.104.2 and headscale ≤ v0.29.6.

How it works

Tailscale SSH does no user-level authentication: the device identity from WireGuard is checked against one policy rule. A session carries forwarding rights for its lifetime.

  • Findings 1 to 3 (High): tailscaled runs ssh -L and -R unix-socket forwarding as root. An unprivileged session can dial root-only sockets such as dockerd, or replace root-owned sockets (BindUnlink). A rename(2) race loop (about 170k flips/s) defeats the {home, /tmp, /run/user} path allowlist. Demonstrated outcomes:
  • root through Docker, with the host filesystem mounted;
  • a root shell via /run/systemd/private (race delivery intermittent, 0 to 4%);
  • capture of an authkey from POST /localapi/v0/start, used to enrol a rogue node on a lock-disabled tailnet;
  • arbitrary root chmod 0600 and deletion of arbitrary root-owned files.
  • Finding 4 (High): in the experimental conn25 app connector, the client-supplied destination is checked only for address family and pool. A user with any app entitlement can reach cloud metadata (IMDS), the connector's LAN and, under the Kubernetes operator, the service and pod CIDRs. Tailnet destinations fail at WireGuard cryptokey routing.
  • Finding 5 (High): gliderssh window-change and environment churn exhausted memory, about 536 MB in 7 s.
  • Finding 6 (Medium-High): headscale has two memory-exhaustion issues. One is authenticated embedded-DERP growth. The other is pre-auth: 1024 tiny registrations evict pending approvals and block onboarding. Separately, the client's derphttp read is unbounded (10 MB became 1.34 GB).
  • Finding 7 (High): serve --tcp to the localapi socket gives a remote peer PermitWrite (node takeover). The unix HTTP-proxy path has a guard that the TCP path lacks.
  • Findings 8 to 11:
  • 8: the serve socket guard is checked only at configuration time (TOCTOU).
  • 9: the funnel node attribute is enforced only by the CLI.
  • 10: TS_SERVE_ALLOW_ALL_INTERFACES exposes serve to the LAN.
  • 11: an h2 shared-context race bled capability headers. 8 of 4000 /admin requests returned 200 instead of 403.

Preconditions / Affected

  • Findings 1 to 3 need a policy-permitted SSH session with forwarding allowed. Any user on an entitled device qualifies, with no sudo.
  • Finding 4 needs conn25 configured and an ordinary app entitlement.
  • Finding 6's registration-cache lockout needs no account, only an internet-reachable headscale.
  • On headscale v0.29.5, SSH forwarding cannot be disabled, so the report treats every SSH grant as root-equivalent.
  • Check mode gates only session setup, so it does not stop forwarding. On the SaaS control plane, one approval covers all check-mode destinations for the window.

What to do

Upstream fixes on main (not yet released):

  • Findings 1 to 3: forwarding moved to unprivileged, credentialed children (PRs #21941 to #21944, #21959, #21963).
  • Finding 5: PRs #21945 and #21953.
  • Finding 7: PR #21966.
  • Finding 11: PR #21931.
  • Finding 6, client read: PR #21980.
  • Headscale registration cache: pending PR #3586.
  • Tailscale targeted a patched client release by the end of 27 October 2026, subject to testing.

Interim mitigations, in the report's order of effect:

  1. Use per-pair SSH grants. Avoid identity-wide or tag-wide accept rules and root targets.
  2. On SaaS, set the per-rule forwarding fields to false.
  3. Remove any serve --tcp <port> unix:<path> that targets the agent's own socket or other privileged paths.
  4. Remove the conn25 capability from clients that do not need it. Apply a default-deny egress NetworkPolicy in Kubernetes. On connector hosts, use the netfilter allowlist shipped as mitigation-conn25/.
  5. Add nftables cgroup rules rejecting tailscaled output to loopback, link-local and RFC1918 unless allowlisted. These do not cover unix sockets.
  6. Set node-key expiry on server fleets and use TPM or Secure Enclave bound state.
  7. For headscale, set MemoryMax, supervise restarts, rate-limit registration at a reverse proxy, and keep it off the open internet.
  8. Detect with object-scoped auditd rules on unix-socket unlink, bind and chmod in /tmp, homes, /run/user and the agent socket paths. Alert on sustained rename storms (about 10^5/s). Treat a socket with mode 0755 where 0600 is expected as a sign of a won race. Alert on authkey use outside change windows.
  9. On suspected compromise, delete the device at the control plane. Reimaging does not revoke copied state files. Then rotate reachable authkeys.

Caveats

  • Most findings were reproduced in a lab on unmodified v1.104.2 and v0.29.5. Finding 10 is source-verified only. The allowlist-sharing interaction in finding 4 is analysis, not lab-demonstrated. Authkey capture was demonstrated, and lock-enabled tailnets were shown only by in-transit capture, not a real disable.
  • Race delivery is intermittent, and the report notes that strace closes the race window.
  • Stock OpenSSH is a structural analogue only, and the report says the effect is not demonstrated there. Dropbear CVE-2025-14282 (fixed in 2025.89) is the same class on the dial side.
  • Windisch argues that Tailscale's design choices weaken defence in depth. That is the researcher's opinion. Tailscale's own assessment emphasises escalation beyond the configured local user's permissions.

Organisations named here

  • conn25
  • Cyberdione Labs
  • Erica Windisch
  • gliderssh
  • Tailnet Lock

CVEs named here

  • CVE-2025-14282

Technologies named here

  • Tailscale SSH
  • tailscale serve / funnel
  • gliderssh, inherited from upstream gliderlabs/ssh v0.3.8
  • Tailscale Kubernetes operator
  • conn25 / app connectors
  • Tailnet Lock
  • Docker daemon, systemd, kubelet
  • racer.py and attack.sh
  • poc-docker/tailscale-privesc.sh
  • mitigation-conn25/
  • poc-servefunnel/
  • busctl
  • Tailscale
  • headscale
  • SSH
  • Docker
  • Kubernetes
  • systemd
  • The Hacker News

Techniques named here

  • Finding 1
  • Finding 2
  • Finding 4
  • Finding 5
  • Finding 6
  • Finding 7
  • Finding 8
  • Finding 9
  • Finding 10
  • Finding 11
  • Recon
  • TOCTOU
  • Privilege escalation

Threat actors named here

  • Tailworm