TS-2025-008

Vulnerability from tailscale - Published: Wed, 19 Nov 2025 00:00:00 GMT

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 tailscaled daemon, the state directory is set by the --statedir or --state flags.
  • If you use the default systemd unit files distributed with the official Tailscale deb, rpm, and tar.gz packages, the --state flag is set automatically.
  • If you use Tailscale in Docker or Kubernetes, the state directory is set by the TS_STATE_DIR environment 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:

  1. Tailnet Lock is enabled in the tailnet,
  2. At least two nodes were running the tailscaled daemon without the --statedir/--state flags, or without the TS_STATE_DIR environment variable, and
  3. 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:

  1. Review the configuration of any nodes that run the tailscaled daemon. Specifically, check the startup parameters for the tailscaled service. Any nodes running tailscaled without a state directory are potentially vulnerable until upgraded.

  2. Upgrade all nodes running the tailscaled daemon to 1.90.8 or later. Alternatively, set a state directory by:

    • Adding the --statedir flag if you run the tailscaled daemon, or
    • Adding the TS_STATE_DIR environment variable if you run Tailscale in Docker or Kubernetes.

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.

Show details on source website

{
  "guidislink": false,
  "id": "https://tailscale.com/security-bulletins/#ts-2025-008",
  "link": "https://tailscale.com/security-bulletins/#ts-2025-008",
  "links": [
    {
      "href": "https://tailscale.com/security-bulletins/#ts-2025-008",
      "rel": "alternate",
      "type": "text/html"
    }
  ],
  "published": "Wed, 19 Nov 2025 00:00:00 GMT",
  "summary": "\u003cp\u003e\u003cstrong\u003e\u003cem\u003eDescription\u003c/em\u003e\u003c/strong\u003e: Nodes without \u003ccode\u003e--statedir\u003c/code\u003e or \u003ccode\u003eTS_STATE_DIR\u003c/code\u003e failed to enforce signing checks in tailnets with Tailnet Lock enabled.\u003c/p\u003e\n\u003ch4\u003eWhat happened?\u003c/h4\u003e\n\u003cp\u003eIn tailnets where \u003ca href=\"https://tailscale.com/kb/1226/tailnet-lock\"\u003eTailnet Lock\u003c/a\u003e is enabled, unsigned nodes running the \u003ccode\u003etailscaled\u003c/code\u003e daemon (for example, on Linux) without specifying a \u003ccode\u003e--statedir\u003c/code\u003e or \u003ccode\u003e--state\u003c/code\u003e 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.\u003c/p\u003e\n\u003cp\u003eWhen Tailnet Lock is enabled, Tailscale nodes will only communicate if their node keys have been signed by a trusted signing node.\u003c/p\u003e\n\u003cp\u003eThe set of trusted signing nodes is tracked by the \u003ca href=\"https://tailscale.com/kb/1226/tailnet-lock#tailnet-key-authority\"\u003etailnet key authority (TKA)\u003c/a\u003e. 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.\u003c/p\u003e\n\u003cp\u003eThe TKA state is stored on disk in the state directory.\nPrior to 1.90.8, this was the only storage option.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eIf you use the macOS, Windows, iOS, Android, and tvOS clients, the state directory is set automatically.\u003c/li\u003e\n\u003cli\u003eIf you run the \u003ccode\u003etailscaled\u003c/code\u003e daemon, the state directory is set by the \u003ca href=\"https://tailscale.com/kb/1278/tailscaled#flags-to-tailscaled\"\u003e\u003ccode\u003e--statedir\u003c/code\u003e\u003c/a\u003e or \u003ccode\u003e--state\u003c/code\u003e flags.\u003c/li\u003e\n\u003cli\u003eIf you use the default systemd unit files distributed with the official Tailscale \u003ccode\u003edeb\u003c/code\u003e, \u003ccode\u003erpm\u003c/code\u003e, and \u003ccode\u003etar.gz\u003c/code\u003e packages, the \u003ccode\u003e--state\u003c/code\u003e flag is set automatically.\u003c/li\u003e\n\u003cli\u003eIf you use Tailscale in Docker or Kubernetes, the state directory is set by the \u003ca href=\"https://tailscale.com/kb/1282/docker#ts_state_dir\"\u003e\u003ccode\u003eTS_STATE_DIR\u003c/code\u003e environment variable\u003c/a\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eIf \u003ccode\u003etailscaled\u003c/code\u003e was started without a state directory (\u003ccode\u003e--statedir\u003c/code\u003e, \u003ccode\u003e--state\u003c/code\u003e, and/or \u003ccode\u003eTS_STATE_DIR\u003c/code\u003e 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.\u003c/p\u003e\n\u003ch4\u003eWhat was the impact?\u003c/h4\u003e\n\u003cp\u003e\u003cstrong\u003eThere is no indication of this issue being exploited in the wild.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eThe bug allowed communication between two or more unsigned nodes, each running without a state directory, even though Tailnet Lock was enabled.\u003c/p\u003e\n\u003cp\u003eWhile the bug is present across many versions, the risk of exploitation is low. It was not possible for an unsigned node without a \u003ccode\u003e--statedir\u003c/code\u003e 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.\u003c/p\u003e\n\u003ch4\u003eWho was affected?\u003c/h4\u003e\n\u003cp\u003eThe vulnerability only manifests when all three of the following conditions are met:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eTailnet Lock is enabled in the tailnet,\u003c/li\u003e\n\u003cli\u003eAt least two nodes were running the \u003ccode\u003etailscaled\u003c/code\u003e daemon without the \u003ccode\u003e--statedir\u003c/code\u003e/\u003ccode\u003e--state\u003c/code\u003e flags, or without the \u003ccode\u003eTS_STATE_DIR\u003c/code\u003e environment variable, and\u003c/li\u003e\n\u003cli\u003eAt least one of the node keys was not signed.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eThis bug was present in all Tailscale clients from the introduction of Tailnet Lock, and is fixed in version 1.90.8.\u003c/p\u003e\n\u003cp\u003eYou can tell if a node is affected by this issue by running \u003ccode\u003etailscale lock status\u003c/code\u003e.\nIf the output says \u003ccode\u003eTailnet Lock is NOT enabled\u003c/code\u003e but Tailnet Lock is enabled in your Tailnet, the node is not enforcing signing checks correctly.\u003c/p\u003e\n\u003cp\u003eAlternatively, you can look for the following text in your \u003ca href=\"https://tailscale.com/kb/1011/log-mesh-traffic\"\u003eclient logs\u003c/a\u003e:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003enetwork-lock unavailable; no state directory\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e(The pre-release name for Tailnet Lock was \"network lock\", which still persists in parts of the codebase.)\u003c/p\u003e\n\u003ch4\u003eWhat do I need to do?\u003c/h4\u003e\n\u003cp\u003eIf you don\u0027t use Tailnet Lock, no action is required.\u003c/p\u003e\n\u003cp\u003eIf you do use Tailnet Lock:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eReview the configuration of any nodes that run the \u003ccode\u003etailscaled\u003c/code\u003e daemon.\u003c/strong\u003e\nSpecifically, check the startup parameters for the \u003ccode\u003etailscaled\u003c/code\u003e service.\nAny nodes running \u003ccode\u003etailscaled\u003c/code\u003e without a state directory are potentially vulnerable until upgraded.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eUpgrade all nodes running the \u003ccode\u003etailscaled\u003c/code\u003e daemon to 1.90.8 or later.\u003c/strong\u003e\nAlternatively, set a state directory by:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eAdding the \u003ccode\u003e--statedir\u003c/code\u003e flag if you run the \u003ccode\u003etailscaled\u003c/code\u003e daemon, or\u003c/li\u003e\n\u003cli\u003eAdding the \u003ccode\u003eTS_STATE_DIR\u003c/code\u003e environment variable if you run Tailscale in Docker or Kubernetes.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eIn version 1.90.8 and later, nodes without a state directory will now store the TKA in memory and enforce Tailnet Lock signing requirements.\u003c/p\u003e\n\u003cp\u003eHowever, nodes that store TKA in memory must re-fetch the complete TKA from the coordination server whenever they start.\n\u003cstrong\u003eWe strongly recommend setting a state directory for all nodes\u003c/strong\u003e, allowing them to store the TKA on disk and eliminate the startup control plane dependency.\u003c/p\u003e",
  "summary_detail": {
    "base": "https://tailscale.com/security-bulletins/index.xml",
    "language": null,
    "type": "text/html",
    "value": "\u003cp\u003e\u003cstrong\u003e\u003cem\u003eDescription\u003c/em\u003e\u003c/strong\u003e: Nodes without \u003ccode\u003e--statedir\u003c/code\u003e or \u003ccode\u003eTS_STATE_DIR\u003c/code\u003e failed to enforce signing checks in tailnets with Tailnet Lock enabled.\u003c/p\u003e\n\u003ch4\u003eWhat happened?\u003c/h4\u003e\n\u003cp\u003eIn tailnets where \u003ca href=\"https://tailscale.com/kb/1226/tailnet-lock\"\u003eTailnet Lock\u003c/a\u003e is enabled, unsigned nodes running the \u003ccode\u003etailscaled\u003c/code\u003e daemon (for example, on Linux) without specifying a \u003ccode\u003e--statedir\u003c/code\u003e or \u003ccode\u003e--state\u003c/code\u003e 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.\u003c/p\u003e\n\u003cp\u003eWhen Tailnet Lock is enabled, Tailscale nodes will only communicate if their node keys have been signed by a trusted signing node.\u003c/p\u003e\n\u003cp\u003eThe set of trusted signing nodes is tracked by the \u003ca href=\"https://tailscale.com/kb/1226/tailnet-lock#tailnet-key-authority\"\u003etailnet key authority (TKA)\u003c/a\u003e. 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.\u003c/p\u003e\n\u003cp\u003eThe TKA state is stored on disk in the state directory.\nPrior to 1.90.8, this was the only storage option.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eIf you use the macOS, Windows, iOS, Android, and tvOS clients, the state directory is set automatically.\u003c/li\u003e\n\u003cli\u003eIf you run the \u003ccode\u003etailscaled\u003c/code\u003e daemon, the state directory is set by the \u003ca href=\"https://tailscale.com/kb/1278/tailscaled#flags-to-tailscaled\"\u003e\u003ccode\u003e--statedir\u003c/code\u003e\u003c/a\u003e or \u003ccode\u003e--state\u003c/code\u003e flags.\u003c/li\u003e\n\u003cli\u003eIf you use the default systemd unit files distributed with the official Tailscale \u003ccode\u003edeb\u003c/code\u003e, \u003ccode\u003erpm\u003c/code\u003e, and \u003ccode\u003etar.gz\u003c/code\u003e packages, the \u003ccode\u003e--state\u003c/code\u003e flag is set automatically.\u003c/li\u003e\n\u003cli\u003eIf you use Tailscale in Docker or Kubernetes, the state directory is set by the \u003ca href=\"https://tailscale.com/kb/1282/docker#ts_state_dir\"\u003e\u003ccode\u003eTS_STATE_DIR\u003c/code\u003e environment variable\u003c/a\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eIf \u003ccode\u003etailscaled\u003c/code\u003e was started without a state directory (\u003ccode\u003e--statedir\u003c/code\u003e, \u003ccode\u003e--state\u003c/code\u003e, and/or \u003ccode\u003eTS_STATE_DIR\u003c/code\u003e 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.\u003c/p\u003e\n\u003ch4\u003eWhat was the impact?\u003c/h4\u003e\n\u003cp\u003e\u003cstrong\u003eThere is no indication of this issue being exploited in the wild.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eThe bug allowed communication between two or more unsigned nodes, each running without a state directory, even though Tailnet Lock was enabled.\u003c/p\u003e\n\u003cp\u003eWhile the bug is present across many versions, the risk of exploitation is low. It was not possible for an unsigned node without a \u003ccode\u003e--statedir\u003c/code\u003e 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.\u003c/p\u003e\n\u003ch4\u003eWho was affected?\u003c/h4\u003e\n\u003cp\u003eThe vulnerability only manifests when all three of the following conditions are met:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eTailnet Lock is enabled in the tailnet,\u003c/li\u003e\n\u003cli\u003eAt least two nodes were running the \u003ccode\u003etailscaled\u003c/code\u003e daemon without the \u003ccode\u003e--statedir\u003c/code\u003e/\u003ccode\u003e--state\u003c/code\u003e flags, or without the \u003ccode\u003eTS_STATE_DIR\u003c/code\u003e environment variable, and\u003c/li\u003e\n\u003cli\u003eAt least one of the node keys was not signed.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eThis bug was present in all Tailscale clients from the introduction of Tailnet Lock, and is fixed in version 1.90.8.\u003c/p\u003e\n\u003cp\u003eYou can tell if a node is affected by this issue by running \u003ccode\u003etailscale lock status\u003c/code\u003e.\nIf the output says \u003ccode\u003eTailnet Lock is NOT enabled\u003c/code\u003e but Tailnet Lock is enabled in your Tailnet, the node is not enforcing signing checks correctly.\u003c/p\u003e\n\u003cp\u003eAlternatively, you can look for the following text in your \u003ca href=\"https://tailscale.com/kb/1011/log-mesh-traffic\"\u003eclient logs\u003c/a\u003e:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003enetwork-lock unavailable; no state directory\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e(The pre-release name for Tailnet Lock was \"network lock\", which still persists in parts of the codebase.)\u003c/p\u003e\n\u003ch4\u003eWhat do I need to do?\u003c/h4\u003e\n\u003cp\u003eIf you don\u0027t use Tailnet Lock, no action is required.\u003c/p\u003e\n\u003cp\u003eIf you do use Tailnet Lock:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eReview the configuration of any nodes that run the \u003ccode\u003etailscaled\u003c/code\u003e daemon.\u003c/strong\u003e\nSpecifically, check the startup parameters for the \u003ccode\u003etailscaled\u003c/code\u003e service.\nAny nodes running \u003ccode\u003etailscaled\u003c/code\u003e without a state directory are potentially vulnerable until upgraded.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eUpgrade all nodes running the \u003ccode\u003etailscaled\u003c/code\u003e daemon to 1.90.8 or later.\u003c/strong\u003e\nAlternatively, set a state directory by:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eAdding the \u003ccode\u003e--statedir\u003c/code\u003e flag if you run the \u003ccode\u003etailscaled\u003c/code\u003e daemon, or\u003c/li\u003e\n\u003cli\u003eAdding the \u003ccode\u003eTS_STATE_DIR\u003c/code\u003e environment variable if you run Tailscale in Docker or Kubernetes.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eIn version 1.90.8 and later, nodes without a state directory will now store the TKA in memory and enforce Tailnet Lock signing requirements.\u003c/p\u003e\n\u003cp\u003eHowever, nodes that store TKA in memory must re-fetch the complete TKA from the coordination server whenever they start.\n\u003cstrong\u003eWe strongly recommend setting a state directory for all nodes\u003c/strong\u003e, allowing them to store the TKA on disk and eliminate the startup control plane dependency.\u003c/p\u003e"
  },
  "title": "TS-2025-008",
  "title_detail": {
    "base": "https://tailscale.com/security-bulletins/index.xml",
    "language": null,
    "type": "text/plain",
    "value": "TS-2025-008"
  }
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…