TS-2025-008
Description: Nodes without --statedir or TS_STATE_DIR failed to enforce signing checks in tailnets with Tailnet Lock enabled.
What happened?
In tailnets where Tailnet Lock is enabled, unsigned nodes running the tailscaled daemon (for example, on Linux) without specifying a --statedir or --state failed to enforce the required signing checks. This allowed them to communicate with other similarly misconfigured, unsigned nodes, or with malicious nodes that joined the tailnet. This behaviour bypassed the Tailnet Lock security policy for a specific subset of nodes.
When Tailnet Lock is enabled, Tailscale nodes will only communicate if their node keys have been signed by a trusted signing node.
The set of trusted signing nodes is tracked by the tailnet key authority (TKA). This set is distributed to all nodes in the tailnet, and each node must store this state locally so that it can verify peer signatures.
The TKA state is stored on disk in the state directory. Prior to 1.90.8, this was the only storage option.
- If you use the macOS, Windows, iOS, Android, and tvOS clients, the state directory is set automatically.
- If you run the
tailscaleddaemon, the state directory is set by the--statediror--stateflags. - If you use the default systemd unit files distributed with the official Tailscale
deb,rpm, andtar.gzpackages, the--stateflag is set automatically. - If you use Tailscale in Docker or Kubernetes, the state directory is set by the
TS_STATE_DIRenvironment variable.
If tailscaled was started without a state directory (--statedir, --state, and/or TS_STATE_DIR were omitted), it would be unable to store the set of trusted signing nodes. Rather than failing and reporting an error, such a node erroneously skipped checking whether peer signatures were signed, which is a failure to enforce Tailnet Lock.
What was the impact?
There is no indication of this issue being exploited in the wild.
The bug allowed communication between two or more unsigned nodes, each running without a state directory, even though Tailnet Lock was enabled.
While the bug is present across many versions, the risk of exploitation is low. It was not possible for an unsigned node without a --statedir to connect to a node that did have a state directory, as the latter would correctly enforce the policy and reject the connection from the unsigned peer.
Who was affected?
The vulnerability only manifests when all three of the following conditions are met:
- Tailnet Lock is enabled in the tailnet,
- At least two nodes were running the
tailscaleddaemon without the--statedir/--stateflags, or without theTS_STATE_DIRenvironment variable, and - At least one of the node keys was not signed.
This bug was present in all Tailscale clients from the introduction of Tailnet Lock, and is fixed in version 1.90.8.
You can tell if a node is affected by this issue by running tailscale lock status.
If the output says Tailnet Lock is NOT enabled but Tailnet Lock is enabled in your Tailnet, the node is not enforcing signing checks correctly.
Alternatively, you can look for the following text in your client logs:
network-lock unavailable; no state directory
(The pre-release name for Tailnet Lock was "network lock", which still persists in parts of the codebase.)
What do I need to do?
If you don't use Tailnet Lock, no action is required.
If you do use Tailnet Lock:
-
Review the configuration of any nodes that run the
tailscaleddaemon. Specifically, check the startup parameters for thetailscaledservice. Any nodes runningtailscaledwithout a state directory are potentially vulnerable until upgraded. -
Upgrade all nodes running the
tailscaleddaemon to 1.90.8 or later. Alternatively, set a state directory by:- Adding the
--statedirflag if you run thetailscaleddaemon, or - Adding the
TS_STATE_DIRenvironment variable if you run Tailscale in Docker or Kubernetes.
- Adding the
In version 1.90.8 and later, nodes without a state directory will now store the TKA in memory and enforce Tailnet Lock signing requirements.
However, nodes that store TKA in memory must re-fetch the complete TKA from the coordination server whenever they start. We strongly recommend setting a state directory for all nodes, allowing them to store the TKA on disk and eliminate the startup control plane dependency.
