october 2026 · 4 min

Act as the User, Not as the Daemon

The Tailscale SSH findings that landed this week (Tailscale's write-up, The Hacker News) come down to one design choice. tailscaled runs as root. When you SSH in as alice and forward a Unix socket, the daemon did the forwarding itself -- as root, not as alice. Whenever a privileged process does work on someone's behalf, the only check that holds is doing that work as them. Everything else is a check you have to win every time.

The class is old: the confused deputy. A process with more authority than its caller, acting for the caller, with nothing tying the action to the caller's own permissions.

What Happened

Socket forwarding needs the daemon to open a path the user names. Opened as root, that path can be anything root can reach. The obvious one is /var/run/docker.sock, and anyone who can talk to Docker is root on the box.

Tailscale knew the shape. An earlier bulletin, TS-2026-004, fixed in 1.98.9, added a check on where a requested socket path really leads before dialing it (Konfirmity). The new report, from Erica Windisch at Cyberdione Labs, beat that check with a race: pass the check with a harmless path, swap what the path points at, and let the root-owned dial land on Docker. Konfirmity reports one route to a root shell worked 0 to 4% of the time in the lab -- low, but a race only has to be won once. A related route let an attacker swap a root-owned socket for their own listener (HOL). A sibling bug, TS-2026-016, had the same shape in tailscale serve --tcp to a Unix socket: dialed as root, able to reach the daemon's own local API.

The attacker needs a foothold on a device your policy already lets SSH in, and forwarding allowed. Tailscale says the bugs don't let an unauthenticated attacker reach arbitrary SSH servers. This is about what an authorized login could do after the policy said yes.

The Fix Is the Lesson

Version 1.104.4 doesn't add a better check. Forwarding now runs as the logged-in user, and the kernel's file permissions decide what that user can open (The Hacker News). One command shows why that's the fix and not a workaround:

stat -c '%U:%G %a %n' /var/run/docker.sock

On a typical Linux host that prints root:docker 660 /var/run/docker.sock: owner and group can read and write, everyone else gets nothing. The OS already has the answer to "may alice open this socket". A path check run by root re-implements that answer, and loses to a race. A process running as alice gets the kernel's answer at the moment it opens the socket, so there is nothing left to race.

What I'd Do

echo 'TS_SSH_DISABLE_FORWARDING=1' | sudo tee -a /etc/default/tailscaled
sudo systemctl restart tailscaled

On Linux, tailscaled loads its environment from that file (HOL). Per the advisories, it blocks several of the more serious findings without turning off Tailscale SSH itself.

The Tradeoff

Turning forwarding off breaks real work: VS Code Remote SSH and database tunnels go with it. Running as the user has a cost too -- a root daemon that drops to a user per operation is more code, and more code is where the next bug lives. It's still the right trade, because it moves the decision to the one place that already gets it right.

The same deputy is showing up everywhere agents run. An MCP server or a tool proxy started with your credentials acts for a model that isn't you, and a filter in front of it is the same path check, waiting for its race. Judge it the way the Tailscale report judged tailscaled: by everything it can reach, not by what it's supposed to be asked. My bet is the narrowest identity that can do the job beats any filter you put in front of a broad one.

← all posts