Back to Blog

Tailscale checked the path as text and opened it as root

Tailscale checked the path as text and opened it as root

Tailscale's TS-2026-014 bulletin, dated 26 October, fits in one sentence: the SSH server "validated Unix socket paths lexically but would perform the operations as root". It checked a name and then opened the file with the daemon's privileges, which are root's. Between checking and opening there is room for a race. On the other side, says the same bulletin, sit "root-only sockets such as the Docker socket or the tailscaled local API". The fix landed last night: tag v1.104.4 was created on 27 October at 23:01 UTC. But the specific bug is not what interests us. What interests us is the question it leaves on the table: on your servers, who decides who is root?

The four bulletins, in one table

Tailscale published four on the same day. All four are fixed in the same release. They are worth reading together, because two of them are not about SSH, and plenty of people will close the tab at "this is a Tailscale SSH bug, I don't use that".

Bulletin What broke How far it reaches
TS-2026-014 Unix socket forwarding in Tailscale SSH: path validated lexically, operation performed as root Root-only sockets: Docker's and the tailscaled local API
TS-2026-015 Unbounded memory growth in SSH session handling (window changes and environment variables) The OS OOM-kills the tailscaled process: the node drops off the network
TS-2026-016 tailscale serve with Unix backends: TCP forwards dialled as root, and the symlink guard only ran on configuration reload "Full node takeover" via remote access to the local API
TS-2026-017 Race in the shared serve context under HTTP/2 One application receives another's capability header and makes decisions on it

All four close the same way: "fixed in Tailscale version 1.104.4 or newer". There is no 1.104.3 tag in the public repository — it jumps straight from 1.104.2 to 1.104.4, and between the two sit 42 commits. We will come back to them, because they say more than the notes do.

The sentence that explains all four

The tailscaled daemon runs as root on a normal Linux install — there is a userspace networking mode, but it is not what you have on the server — and for what it does it needs to: it creates a network interface, writes routes and touches the host firewall. That is not a design flaw, it is the price of a VPN that "just works" without asking you for anything. But it makes the daemon the subject of every operation it performs on your behalf. When that process takes a request from a peer along the lines of "open this socket for me" and decides whether it is allowed by looking at the text of the path, what sits in the middle is the gap between checking and acting.

That gap has had a name for decades, and you win that race by changing what sits behind the name at exactly the right moment: a symlink pointing somewhere harmless when it is checked and somewhere else when it is opened. Bulletin TS-2026-016 describes it plainly in the other affected feature: the symlink guard "only ran during configuration reload rather than per dial". It was checked once, up front, and trusted thereafter.

Why "the Docker socket" is the serious part

The bulletin gives two examples of a "root-only socket", and they are not equivalent.

  • The Docker socket. Anyone who can write to /var/run/docker.sock can ask the Docker daemon to start a container with the host filesystem mounted inside it. That is not "access to Docker": it is root on the box, by design, and documented as such for years. On an application server that file exists.
  • The tailscaled local API. It is what the tailscale command itself uses to talk to the daemon. Whoever reaches it controls the node's membership of the network. Down that path, bulletin TS-2026-016 — the serve one, not the SSH one — calls it by its name: "full node takeover".

SSH Unix socket forwarding is old and boring: it has been there since OpenSSH 6.7, in October 2014, and gets used daily to reach a database without exposing it. The difference here is who opened the far end. In ordinary SSH it is opened by a process already carrying your identity. Here it was opened by the daemon.

The real question: who decides who is root on your machine?

Ordinary SSH spreads that decision across things that live on your machine: the sshd configuration, the keys in authorized_keys, PAM, group membership and sudoers. They are files. You read them, version them and audit them without asking anyone.

Tailscale SSH takes all of that out of the path. There are no keys to distribute and no authorized_keys to maintain: that is the pitch, and it is a genuinely good one — anyone who has inherited an estate carrying a 2019 intern's keys knows what we mean. In exchange, who gets in and as which user is decided by a rule in the tailnet policy, which lives in the control plane. And because the connection is terminated not by sshd but by tailscaled, the process that opens your session is the same one that runs the VPN.

It is a legitimate architectural decision. What you need is to know it has been taken, because it changes three things at once: where the rule is written, who can change it, and what happens when that layer has a bug. The third one answered itself last weekend. The other two are still yours to answer.

There is a detail in the documentation that makes this clearer than any bulletin, and almost nobody has read it. Tailscale SSH's "check mode" requires re-authentication — with MFA, if you have it — before a session opens. That sounds exactly like what you want. The small print reads, verbatim: "Once re-authenticated to a destination, the user can access the device and any other device in the tailnet without re-verification for the next 12 hours". And the default check period is twelve hours. Unless you write your own checkPeriod for that connection, the check is per person and per network, not per machine. It is the same conversation we had when we wrote that a site-to-site tunnel has nobody to ask for a second factor: the second factor protects the moment of entry, not what lies behind it.

Which gives us the sentence we take away: a mesh is not segmentation. What decides what reaches what inside a mesh is a list of rules. If that list says "the team's laptops can reach the servers", the mesh is doing precisely what you asked for — and so is the compromised laptop.

What the patch tells you

Security fixes read better in the code than in the note. Between 1.104.2 and 1.104.4 there are 42 commits, and thirty of them touch the same directory, ssh/tailssh. These are not point patches: it is a change of shape. The titles tell the story on their own — "route SSH forwarding through the privileged monitor", "dial TCP forwards as the bound user", "relay Unix forward payloads as the bound user", "launch sessions via the monitor", "enforce SSH policy at the connection level". In plain terms: it no longer checks a name and acts as root; it acts as whoever asked, and the little that genuinely needs privilege goes through one place that grants it. That is the right shape, and it took a weekend of work to get there.

Inside that same diff there are three things that made no headline, and that we find more interesting than the root:

  • The session recording could differ from what actually ran. One commit explains that the recording header stored "a shell-parsed reconstruction of the session command", when the command is handed to the user's shell verbatim, and that the lexer "silently drops unbalanced quotes". The fix is to record the raw command. If you archive those recordings for an audit or an incident report, that is not a detail: it is the difference between evidence and a document you believed was evidence.
  • The same fix was already written in OpenSSH, ten years ago. The OpenSSH 7.4 release notes, dated 19 December 2016, say this: "When privilege separation is disabled, forwarded Unix-domain sockets would be created by sshd(8) with the privileges of 'root' instead of the authenticated user". Reported by Jann Horn of Project Zero. The same bug, in the same place. And what OpenSSH put in the middle was its privilege separation monitor, which is exactly where Tailscale has now arrived: the commit that anchors 1.104.4 is titled "route SSH forwarding through the privileged monitor". The other repeat offender rides in the same diff: the denylist of environment variables grew by GCONV_PATH, LOCPATH, NLSPATH, HOSTALIASES, RES_OPTIONS and, in another commit, GLIBC_TUNABLES — the classics for making a privileged process load a file you choose.
  • And the small things, the ones that give away the rush. Private host keys were being written without 0600 permissions. It took three separate commits for the function that answers "is this root?" to mean the same thing everywhere it is called; one of them is titled "treat numerically-zero uids as root in isRootUser".

What this is not

Three limits, because the headline invites going further than the facts allow.

It is not an anonymous attacker from the internet. You need to already control a device your policy allows to connect to the destination. Tailscale's VP of Engineering, Mike Shaver, put it this way: most of the issues "concern what happens after someone has been authorized to connect, particularly how Unix privileges and SSH forwarding are handled", and "it isn't the same as an unauthenticated attacker being able to connect to arbitrary machines, but the potential impact depends on how SSH access is configured". We think that is an honest description and we sign up to it. It is also, precisely, the scenario a zero-trust mesh is bought for. What it is, and this belongs in the same paragraph: chainable. In lab tests the hop from machine to machine was automatic, under a second per hop and with no user interaction, as far as the policy granted SSH, and it produced both root access on the targets and keys that could be used to enrol new devices on the network. Your blast radius is set by your rule table, not by the severity of the bulletin.

Tailscale SSH is not on by default. Two things are required: running tailscale set --ssh on the destination and writing an ssh rule in the policy. If you have not done both, 014 and 015 do not touch you. 016 and 017 belong to tailscale serve, which is a separate feature and is also enabled separately. And Tailscale is not recommending that everyone switch it off: "based on our current assessment, we're not recommending that everyone disable Tailscale SSH". Neither are we.

And an opinion, labelled as one. The report that surfaced all this is called Tailworm, it is signed by Erica Windisch (Cyberdione Labs), and it lists eleven findings across Tailscale 1.104.2 and headscale 0.29.5, six rated high and four of those six in Tailscale SSH. It went out on Sunday the 25th, with no fixed release available. The researcher sent the report and the proof of concept to the vendor's security contact before publishing; what she did not do was wait for a patch. The fix was tagged on Tuesday night. In between sat two days with the map published and no release to apply. We understand the argument for publishing, because a vendor's clock does not run on its own; on a piece of software that runs as root on the servers of companies with nobody watching the feed on a Sunday, we would have waited. That said: the window has passed and the release is out.

Six checks for this week

None of them costs money, and five of the six can be done before upgrading anything.

  • 1. Find out which nodes actually have it enabled, not which ones you think do. A tailscale status --json on each node will tell you, and so will the tailnet console. On an estate that has been growing for two years, "we turned it on for that migration" is a sentence you hear a lot.
  • 2. Read the ssh section of the policy end to end. Who, to what, and as which user. If a rule ends up granting the root user to a broad group, that is your blast radius, and this week's bulletin did not create it. Ten minutes.
  • 3. Decide whether forwarding is needed at all. If the answer is no — and on most servers it is — TS-2026-014 publishes the mitigation itself: TS_SSH_DISABLE_FORWARDING=1. You can set it today, without waiting for the new release.
  • 4. Look at tailscale serve separately. TS-2026-016 is not SSH. tailscale serve status tells you what is published; if any of it points at a Unix socket, that is another door, with its own list of who reaches it.
  • 5. Upgrade to 1.104.4 and verify the version node by node. Laptops update themselves; Linux servers move at the pace of the distribution repository and of whoever owns the patching window. Run tailscale version node by node: the one that matters is on the box holding the interesting sockets, not on the laptop you are checking from.
  • 6. Put a number on checkPeriod. If the rule does not say, it is twelve hours and it covers the whole network. Choosing twelve hours is fine; inheriting them without knowing is not.

In short

There is nothing exotic in the bug: a privileged process checked a name and then acted with its own permissions. If you put in a mesh to stop exposing ports — and that is an excellent reason to put one in — you moved the decision about who gets into your machines to a new layer. That layer also has versions, also has bugs, and also has a policy somebody wrote in a hurry on a Tuesday. The mesh did not take the decision off your hands; it moved it, and now you have to go and look at it where it is.

Where this fits into what we do

When we build zero-trust access we do not start with the tool, we start with the table: who has to reach what, with which identity, and what happens the day a laptop on that table is in somebody else's hands. That table is what decides whether a week like this one is a patch or an incident, and it is independent of whichever product implements it. The other half is networks and communications: what is exposed, where management traffic enters, and what you would have to touch if you changed mesh tomorrow — because meshes do get changed, and the table should not leave with them. If the layer underneath interests you, we have already written about when we prefer WireGuard and when IPsec, which is the transport almost all of them are built on.

Sources (verified on 28 October 2026): bulletins TS-2026-014, TS-2026-015, TS-2026-016 and TS-2026-017, all four dated 26 October 2026 and fixed in 1.104.4 — tailscale.com/security-bulletins. Tag v1.104.4 created on 27 Oct 2026 at 23:01 UTC, the absence of a 1.104.3 tag, and the 42 commits between 1.104.2 and 1.104.4 (ssh/tailssh, privileged monitor, forwards as the bound user, raw command in recordings, environment-variable denylist, 0600 host keys, isRootUser) — Tailscale's public repository. Tailscale SSH being opt-in, check mode and the 12-hour default period covering the whole tailnet — Tailscale SSH documentation. OpenSSH 7.4 security note (19 Dec 2016), reported by Jann Horn (Project Zero), and the arrival of Unix socket forwarding in OpenSSH 6.7 (6 Oct 2014) — OpenSSH release notes. Statements by Mike Shaver (Tailscale VP of Engineering), the tally of the eleven findings in Erica Windisch's (Cyberdione Labs) Tailworm report across Tailscale 1.104.2 and headscale 0.29.5, the chainable nature of the flaws (automatic hops under a second, no user interaction, plus theft of device-enrolment keys), and the disclosure timeline — SecurityWeek.

Could you say today who can be root on each of your servers?

If the answer is spread across an access policy, a sudoers file and somebody's memory, that is the conversation. We write it down with you, using whatever tool you already have: who reaches what, with which identity, and what gets closed. And if your table is already sound, we will say so and sell you nothing.

Talk to everyWAN

Tags:

Share:

Subscribe to our newsletter

To receive IT stories, everyWAN news and exclusive subscriber offers, sign up to our mailing list

Minorisa de Sistemas Informaticos y Gestión S.L. © 2026
everyWAN
everyWAN