Report vulnerabilities privately through GitHub's private vulnerability reporting on matchory/tslink. Do not open a public issue, pull request or discussion for them.
Include what is affected, how to reproduce it, and the impact you expect. We will confirm receipt, keep you informed while we work on a fix, and credit you in the advisory unless you prefer otherwise.
Vulnerabilities in Tailscale itself go to Tailscale, and those in Docker to the Moby project.
Fixes go into the latest release. Older releases are not patched.
tslink makes the guarantees below, provided the environment properties after
them hold. Each guarantee names who it defends against and which properties
it relies on. Tests that verify a guarantee carry its ID (Guards: G1); a
check in the test suite fails if a guarantee listed here has none. Models of
tslink's state machines verify their design, not the code.
Attackers: a process in a container on a tslink network, as root inside it, with Docker's default capabilities. Assumes: E1, E2, E3, E5.
A container cannot reach the tailnet through the host's own Tailscale, by
sockets or by raw frames, through tslink's veth or through Docker's gateway.
This holds with the plugin setting TSLINK_ISOLATE_HOST_TAILNET at its
default, true.
Attackers: a process in any container on the host, on a tslink network or
any other, as root inside it, with Docker's default capabilities; a host on
the LAN that routes tslink's veth range through the Docker host.
Assumes: E1, E2, E3, and of E5 the mangle table and the plugin's privileges:
this isolation is on whatever TSLINK_ISOLATE_HOST_TAILNET says.
Each tslink container's tailscaled has a veth pair in 10.200.0.0/16, and
the host forwards between them. Nobody reaches a tslink container's veth
address through that, by sockets or by raw frames, past Docker's network
isolation and the tailnet's ACLs, whatever interface the traffic comes from,
a bridge with its own name included. The host forwards to tslink's veths only
replies on connections their container opened, and WireGuard (UDP 41641)
from other tslink veths, so colocated nodes keep their direct path. That UDP
port is open to tslink's other containers: WireGuard drops what is not from a
peer.
Attackers: the author of a stack file that passed review. Assumes: E1, E2, E3, E4, E8.
A network that belongs to a stack gives tailnet nodes only to that stack's
tasks. With the cluster credential, a stack's nodes get only the tag
tag:<stack>, with the plugin setting TSLINK_TAG_SCOPE at its default,
exact; prefix also allows tag:<stack>-*, which lets a stack claim a
longer-named stack's base tag. Networks outside a stack cannot use the
cluster credential.
Attackers: a process in a container on a tslink network; a tailnet peer; the control server. Assumes: E2, E3. Manual: OAuth client secrets (the cluster credential) are searched for by hand on a test tailnet before each release.
Auth keys and OAuth client secrets do not appear in these places:
- the plugin log;
- the command lines of tailscale and tailscaled;
- the environment and the log of tailscaled;
- status files;
- the output of
tslink diag.
Persons with access to the Docker API can read them in network options and plugin settings. See "Scope".
| ID | Property | Ensured by | tslink diag --preflight |
|---|---|---|---|
| E1 | Containers on tslink networks are not privileged and have neither NET_ADMIN nor SYS_ADMIN |
stack review, CI policy | checks |
| E2 | No container on a tslink network bind-mounts Docker's socket, /run/netns, Docker's data root (/var/lib/docker by default), the plugin's data directory or a directory containing them, or shares the host's PID namespace |
stack review, CI policy | checks |
| E3 | Only operators have Docker API access and root on hosts | operator | does not check |
| E4 | Stacks are deployed with docker stack deploy, which sets com.docker.stack.namespace |
operator, CI | lists Swarm tasks outside stacks |
| E5 | The host supports the iptables mangle table for IPv4 and IPv6, the plugin runs with the privileges of its config.json, and its setting TSLINK_ISOLATE_HOST_TAILNET is at its default, true |
operator | checks the setting, and that tslink's isolation chains lead FORWARD and INPUT |
| E6 | The plugin is installed from a release image pinned by digest | operator | checks the digest pin |
| E7 | The shared certificate directory is owned by root, mode 0700 or stricter, and reachable only by the cluster's hosts | operator | checks owner and mode, with --shared-dir |
| E8 | The cluster credential owns only stack-prefixed tags, stack names follow tslink's rules, and the plugin setting TSLINK_TAG_SCOPE is at its default, exact |
operator, tailnet policy | checks the tag scope setting |
| E9 | Tailnet Lock is on where peer identity must not depend on the control server | operator | reports whether it is on |
Run tslink diag --preflight on each host, and on each node of a Swarm. It
checks only the properties that it can see on the host where it runs.
tslink diag shows how.
tslink is privileged by design. Think of this when you assess the impact of a finding:
- The plugin runs as root on the host with host networking,
CAP_NET_ADMIN,CAP_SYS_ADMIN,/dev/net/tun, the Docker socket, the host's/runand/var/lib/docker-plugins/tailscale. It enters container network namespaces and changes interfaces, routes and firewall rules. A flaw that lets a container or a tailnet peer influence what the plugin does on the host is in scope. - Credentials are visible to persons with access to the Docker API.
docker network inspectshows the network optiontslink.authkey, anddocker plugin inspectshows the plugin settingTS_AUTHKEY. On a Swarm, the Raft store of the managers keeps them. Access to the Docker API is root on the host, so this is expected and not a vulnerability. See Where credentials are visible. A credential that leaks to another place is in scope, for example to a log, the process list of the host or the application container. - Isolation between containers and stacks is in scope. Examples:
- A container reaches the tailnet through the Tailscale of the host or of another container.
- A task gets tags or an identity that its network does not give it.
- A stack uses the network or the state of another stack.
- State on disk:
/var/lib/docker-plugins/tailscaleholds each node's Tailscale state (its private keys). Anyone who can read it can impersonate those nodes.