# Tailworm : 11 failles non corrigées dans Tailscale, dont un accès root via Tailscale SSH

> Une chercheuse a publié onze failles dans Tailscale 1.104.2 et headscale 0.29.5 sans attendre de correctif. La plus grave : le transfert de sockets de Tailscale SSH se faisait en root. Ce que dit le rapport, ce que Tailscale confirme, et les réglages à faire en attendant la version corrigée.


*Ou : Comment un réseau « zéro confiance » faisait entièrement confiance à son propre démon*

Le 25 octobre 2026, Erica Windisch, de Cyberdione Labs, a publié Tailworm : onze failles dans le client Tailscale 1.104.2 et dans headscale 0.29.5, le serveur de coordination libre qu'utilisent ceux qui hébergent eux-mêmes leur réseau Tailscale[^1]. Six sont jugées de gravité élevée. La pire touche Tailscale SSH, le serveur SSH intégré à l'agent : quand un utilisateur transférait un socket Unix, l'agent ouvrait la connexion avec les droits root. Une machine compromise du réseau privé, le « tailnet », peut alors devenir root sur les serveurs que la politique d'accès lui ouvre. La chercheuse a envoyé son rapport à Tailscale la veille de le publier. Le 27 octobre, Tailscale confirme des failles sérieuses, a fusionné des correctifs et vise une version corrigée dans la journée. Pas encore de version, pas de CVE.

Le rapport s'ouvre sur une seule phrase, en haut de la page : « we have zero-trust in our infrastructure ». Le jeu de mots est cruel parce qu'il est exact. Tailscale se vend comme un accès « Zero Trust » fondé sur l'identité : aucune machine n'en croit une autre sur la foi de son adresse, chaque connexion passe par une identité et une politique. Il restait pourtant quelqu'un à qui tout le monde faisait confiance : l'agent `tailscaled` lui-même, qui tourne en root.

## Faille Tailscale SSH : comment une machine compromise devient root sur les autres

### Ce que fait Tailscale SSH, et avec quels droits

Tailscale construit un [réseau maillé entre les machines](/guides/vpn-professionnel/comparatifs-vpn/nordlayer-vs-tailscale-twingate/), chiffré par WireGuard. Tailscale SSH ajoute un serveur SSH dans l'agent : plus de clés à distribuer, l'identité du tailnet sert d'authentification, et la politique d'accès dit qui ouvre une session, sous quel compte local, sur quelle machine[^2]. Sur Linux, l'agent tourne en root, ne serait-ce que pour créer son interface réseau. Les sessions, elles, s'exécutent sous le compte de l'utilisateur.

Le transfert de sockets Unix faisait exception. Avec `ssh -L`, on demande au serveur de se connecter à un socket de la machine distante et de relayer le trafic. OpenSSH fait cette connexion depuis un processus qui a déjà pris l'identité de l'utilisateur authentifié[^3]. Tailscale SSH la faisait depuis `tailscaled`, donc en root. Il limitait bien les chemins au dossier personnel, à `/tmp` et à `/run/user/<uid>`, mais une fois le chemin accepté, c'était root qui composait le numéro.

C'est un standard d'hôtel. Le client demande à être mis en relation avec un poste. La réceptionniste vérifie que le poste est à son étage, puis appelle depuis la ligne du directeur, à laquelle tous les postes répondent, coffre compris. Dans le laboratoire de la chercheuse, le coffre était un démon Docker qui écoutait dans un dossier permis : résultat, `uid=0(root)` et le contenu de `/etc/shadow`, sans `sudo` ni groupe `docker`[^1].

### Le correctif de juin, et la course d'octobre

Ce transfert avait déjà mené au socket Docker. Le 3 juin 2026, le bulletin TS-2026-004 décrivait la même porte : la vérification comparait le texte du chemin demandé à une liste, et un lien symbolique posé dans un dossier permis menait où il voulait[^4]. La correction, livrée dans la version 1.98.9 : résoudre les liens, puis vérifier la destination.

Tailworm montre la limite de cette correction. Vérifier, puis utiliser, ce sont deux instants. Entre la résolution du lien et la connexion, l'utilisateur peut faire pointer le lien ailleurs, et la chercheuse le fait basculer environ 170 000 fois par seconde. Il suffit qu'une bascule tombe au bon moment. C'est la course que les anglophones appellent TOCTOU, *time of check, time of use*, et elle rend la liste des dossiers permis décorative.

Pour voir la taille de la fenêtre, j'ai reproduit le motif en Go, avec des fichiers à moi et sans aucun privilège : un dossier permis, un fichier public dedans, un fichier secret à côté, et un lien qu'une goroutine fait basculer de l'un à l'autre par renommage atomique. La fonction qui vérifie :

```go
// Résoudre, comparer, puis lire : deux instants, et le lien peut changer entre les deux
func verifierPuisLire(dossier, chemin string) ([]byte, error) {
	reel, err := filepath.EvalSymlinks(chemin)
	if err != nil {
		return nil, err
	}
	if !strings.HasPrefix(reel, dossier+string(filepath.Separator)) {
		return nil, errRefus
	}
	return os.ReadFile(chemin) // le noyau résout le lien une seconde fois
}
```

Et une exécution de 200 000 essais, avec en seconde ligne la même lecture par `os.Root` (Go 1.27.3, Linux, Ryzen 7 5800H) :

```text
vérifier puis lire : 16397 secrets lus sur 200000 essais
os.Root            : 0 secrets lus sur 200000 essais
```

Sur cinq exécutions, la fonction a laissé lire le secret entre 16 397 et 17 187 fois, environ 8 % des essais, alors que chaque vérification était juste au moment où elle était faite. `os.Root`, arrivé avec Go 1.24 en février 2025[^5], résout le chemin à partir d'un descripteur ouvert sur le dossier et refuse tout lien qui en sort, au moment même de l'ouverture : la vérification et l'usage ne font plus qu'un.

`os.Root` n'ouvre pas de socket, en revanche. Le correctif de Tailscale, fusionné le 26 octobre, suit le modèle d'OpenSSH : le transfert passe par un processus enfant qui porte l'identité de l'utilisateur connecté, et ce sont les permissions du fichier, vérifiées par le noyau, qui font la frontière[^6]. Plus besoin de deviner où mène un chemin quand on n'a pas plus de droits que celui qui le demande.

### De la machine compromise au nœud pirate

Les autres constats sur SSH prolongent le premier. Avec `ssh -R`, un utilisateur peut remplacer un socket appartenant à root par le sien et se faire passer pour le service. En laboratoire, la chaîne est allée jusqu'à capturer une clé d'inscription en route vers l'API locale de `tailscaled`, puis à faire entrer une machine pirate dans le tailnet sous l'identité de la victime. Il faut pour cela que Tailnet Lock, qui fait signer chaque nouveau nœud par des machines de confiance, soit désactivé, ce qui est le réglage par défaut[^7]. Avec Tailnet Lock, les secrets nécessaires ont été capturés, mais la chercheuse n'a pas exécuté la dernière étape.

Le nom du rapport vient de là : une machine compromise, un accès SSH accordé par la politique, et la propagation peut s'automatiser vers chaque machine autorisée, sans mot de passe ni clé volée, en moins d'une seconde par saut. Le point de départ reste une première machine prise, par une application web vulnérable, un paquet piégé, un hameçonnage ou des identifiants volés. Tout le monde n'accepte pas le mot « ver », et personne d'autre n'a encore reproduit publiquement ces résultats[^8].

Les onze constats, regroupés par composant, avec l'état que donne le rapport au 27 octobre :

| Composant | Constats | Gravité la plus haute | État au 27 octobre |
|---|---|---|---|
| Tailscale SSH (transfert, course, mémoire) | 1, 2, 3, 5 | Élevée | Corrigés dans le dépôt, dans aucune version |
| Connecteurs d'applications (conn25, expérimental) | 4 | Élevée | Non corrigé |
| headscale et relais DERP | 6 | Moyenne à élevée | Côté client corrigé dans le dépôt, côté headscale en cours |
| Serve et Funnel | 7 à 11 | Élevée (constat 7) | 7 et 11 corrigés dans le dépôt, 8 à 10 non ou affaire de configuration |

### Signalé un samedi, publié le dimanche

La chercheuse a envoyé le rapport et ses preuves de concept au contact sécurité de Tailscale le samedi, et publié le dimanche. Elle parle d'une divulgation signalée mais pas coordonnée[^8]. Son argument tient en une ligne du rapport : les infrastructures sont vulnérables aujourd'hui, et plus vite on le sait, plus vite on corrige. Les preuves de concept, elles, n'ont pas été publiées.

L'usage le plus courant est inverse. Project Zero, l'équipe de Google, laisse 90 jours à l'éditeur, puis publie les détails 30 jours après le correctif[^9]. Tailscale a eu un jour, et quelques commentateurs ont jugé la méthode irresponsable. Il faut reconnaître au calendrier une efficacité certaine : la page d'enquête de Tailscale est en ligne le 26 octobre, le correctif SSH fusionné le même jour, la version promise pour le 27. Rien n'accélère un correctif comme un rapport public un dimanche.

Tailscale, de son côté, décrit surtout des élévations de privilèges : un utilisateur déjà autorisé à se connecter par Tailscale SSH pourrait dépasser les droits du compte local prévu. L'entreprise vérifie encore quelles configurations sont touchées et ne recommande pas à tout le monde de couper Tailscale SSH[^10]. Son vice-président de l'ingénierie, Mike Shaver, a ajouté sur Reddit qu'elle examinait déjà ces zones avec des spécialistes extérieurs[^8].

**DevOps Dave :** On a tout mis derrière Tailscale. Plus un seul port ouvert. Zéro confiance.

**Security Sarah :** Qui a le droit de faire du SSH vers le serveur de base de données ?

**DevOps Dave :** Tout le groupe des développeurs. C'est plus simple qu'une règle par personne.

**Security Sarah :** Donc un paquet piégé sur un seul portable ouvre la base.

**DevOps Dave :** Seulement depuis le tailnet !

**Security Sarah :** Le portable est dans le tailnet, Dave. C'est pour ça qu'on l'y a mis.

### Que régler en attendant la version corrigée

D'abord, installer la version corrigée dès sa sortie : Tailscale promet un bulletin avec les versions et les configurations concernées. D'ici là, si le transfert ne sert à rien, une variable d'environnement le coupe dans l'agent, sans couper Tailscale SSH[^10] :

```bash
# Paquet Debian ou Ubuntu : le service lit ses variables dans ce fichier
echo 'TS_SSH_DISABLE_FORWARDING=1' | sudo tee -a /etc/default/tailscaled
sudo systemctl restart tailscaled
```

Je n'utilise pas Tailscale et je n'ai pas exécuté ces deux lignes. J'ai lu le code de la version 1.104.2 : la variable y est déclarée et vérifiée avant chacun des quatre transferts, TCP et Unix, dans les deux sens, et le fichier de service fourni par Tailscale charge bien `/etc/default/tailscaled`.

Le reste, d'après le rapport et la page de Tailscale :

- **Tailscale SSH inutile :** `sudo tailscale set --ssh=false`, après s'être assuré d'un autre accès à la machine.
- **Règles d'accès :** une source vers une destination, plutôt qu'un groupe ou un tag entier, et jamais vers `root` ni un compte qui a `sudo` sans mot de passe. Le mode de vérification renforcé (`check`) ne protège pas le transfert : il ne contrôle que l'ouverture de session.
- **headscale :** en 0.29.5, la politique ne sait pas interdire le transfert. La variable de l'agent est le seul interrupteur ; à défaut, chaque accès SSH accordé vaut un accès root.
- **Connecteurs et Serve :** retirer la capacité de connecteur d'applications là où elle ne sert pas, et tout `tailscale serve` qui pointe vers un socket Unix.
- **Machine compromise :** la supprimer côté serveur de coordination. Réinstaller le système ne suffit pas, l'état copié reste valable.

Le rapport ne dit rien d'autre que ce que dit le [principe du Zero Trust](/guides/vpn-professionnel/vpn-pme-teletravail/ztna-pme-au-dela-du-vpn/) : ne faire confiance à aucune machine sur la foi de sa position. Il oublie juste une exception, celle qui applique la règle. C'est la même erreur que [faire confiance à localhost](/blog/clawdbot-autopsie-localhost-trust/), un étage plus bas : la frontière n'est pas l'endroit d'où vient la demande, c'est l'identité sous laquelle on l'exécute. Cet article décrit la situation au 27 octobre 2026, en début d'après-midi, heure de Paris ; la version corrigée devrait suivre de près. Le bulletin dira combien des onze constats Tailscale retient.

---

[^1]: Le rapport [Tailworm](https://tailworm.com/) de Cyberdione Labs : tests en laboratoire du 22 au 24 octobre 2026 sur Tailscale 1.104.2 et headscale 0.29.5, constats numérotés de 1 à 11, état des correctifs mis à jour le 27 octobre.

[^2]: Documentation de [Tailscale SSH](https://tailscale.com/docs/features/tailscale-ssh).

[^3]: Le fichier [README.privsep](https://github.com/openssh/openssh-portable/blob/master/README.privsep) d'OpenSSH : après l'authentification, le processus de session abandonne les droits root pour ceux de l'utilisateur authentifié.

[^4]: Les [bulletins de sécurité de Tailscale](https://tailscale.com/security-bulletins), TS-2026-004. La même année, TS-2026-006 et TS-2026-009 décrivaient deux autres chemins vers une session root par Tailscale SSH, l'un par l'identifiant numérique `0`, l'autre par un nom d'utilisateur commençant par un tiret, lu comme une option par `getent`. Les trois sont corrigés dans la version 1.98.9. Tailworm est donc au moins le quatrième chemin de l'année.

[^5]: [Notes de version de Go 1.24](https://go.dev/doc/go1.24), publiées en février 2025. `Root.ReadFile`, utilisé dans mon test, est venu avec Go 1.25.

[^6]: La [PR #21942](https://github.com/tailscale/tailscale/pull/21942) du dépôt tailscale/tailscale, fusionnée le 26 octobre 2026 : chaque connexion SSH reçoit un processus moniteur, et les sockets transférés sont ouverts ou créés par des processus enfants qui portent l'identité de l'utilisateur. Les permissions du noyau remplacent la vérification par résolution des liens symboliques.

[^7]: Documentation de [Tailnet Lock](https://tailscale.com/docs/features/tailnet-lock).

[^8]: [The Hacker News](https://thehackernews.com/2026/10/unpatched-tailscale-ssh-flaws-could-let.html), article du 25 octobre 2026 mis à jour le 27 octobre.

[^9]: La [politique de divulgation de Project Zero](https://projectzero.google/vulnerability-disclosure-policy.html) : 90 jours pour corriger, puis 30 jours après la sortie du correctif avant la publication des détails.

[^10]: La [page d'enquête de Tailscale sur Tailscale SSH](https://tailscale.com/ssh-investigation-update), publiée le 26 octobre 2026.



