# Security Policy
This is a pure-Rust, work-in-progress reimplementation of the Tailscale `tsnet` node. It is shared
in the open out of a belief in open source, but it is **experimental software**. This document is an
honest account of its current security posture so that anyone considering routing real traffic
through it can make an informed decision.
> [!CAUTION]
> All code linked against this library must set `TS_RS_EXPERIMENT=this_is_unstable_software` before
> the process starts. That gate exists *because* of the limitations below — most importantly the
> unaudited cryptography. **Do not remove the gate to make a build look production-ready.** It is
> meant to stay until an independent audit lands and any resulting fixes ship.
## Trust boundary
```mermaid
flowchart LR
subgraph Embedder["Embedding application (your responsibility)"]
State["State dir<br/>auth keys · WG private keys · ACME account keys<br/>(plaintext on disk)"]
end
subgraph Lib["tailscale-rs (this fork)"]
CtrlNoise["Control plane (TS2021 Noise)<br/>hand-implemented"]
DataNoise["Data plane (WireGuard Noise_IKpsk2)<br/>hand-implemented"]
Acme["ACME / JWS (RFC 8555)<br/>hand-implemented"]
TKA["Tailnet Lock verify<br/>wired, ENFORCEMENT INERT"]
Dialer["RealDialer chokepoint<br/>fail-closed, IPv4-only"]
end
Control["Control plane<br/>(Tailscale or self-hosted)"]
Peers["Tailnet peers"]
Upstream["Upstream proxy / Internet"]
State --> Lib
CtrlNoise <--> Control
DataNoise <--> Peers
Acme <--> Control
TKA -. "cannot yet block<br/>compromised control" .-> Control
Dialer --> Upstream
```
The control plane is trusted today: Tailnet Lock, the mechanism that would let a client reject
peer node-keys injected by a malicious or compromised control plane, is wired but **not yet
enforcing** (see below).
## Unaudited cryptography
This fork hand-implements its cryptographic protocols and **has not undergone an independent
cryptographic or security audit**. The hand-rolled surfaces include:
- **WireGuard data-plane handshake** — `Noise_IKpsk2` (`ts_tunnel/src/handshake.rs`).
- **Control-plane handshake** — the TS2021 control Noise.
- **ACME / JWS** — RFC 8555 certificate issuance and JWS signing (`ts_control/src/acme.rs`).
- **Exit dialer** — a hand-rolled SOCKS5 (RFC 1928/1929) and HTTP `CONNECT` client (zero extra
dependencies, to keep the egress path `ring`-only and musl-clean).
- **Tailnet Lock (TKA) CBOR** — the CTAP2-canonical CBOR encoding in `ts_tka` is **not**
cross-validated against Go-produced test vectors. Byte-for-byte wire compatibility with a live
Tailscale TKA is asserted by construction, not proven (see the `ts_tka` crate module docs). A
*failed* verification is always safe to act on (deny); a *successful* verification should be
treated as advisory until vectors land.
Conservatively, assume there could be a critical flaw in any of these paths. Do not rely on this
library for data privacy until the audit is complete.
## Tailnet Lock (TKA) status
Per-peer key-signature verification is **wired and unit-tested** at the peer-trust chokepoint
(`ts_runtime`'s `peer_tracker`). When an `Authority` carrying a non-empty trusted-key state is
supplied, the chokepoint fails **closed**: a peer with a bad signature is rejected, and a peer that
presents no signature under an active lock is also rejected. Neither is upserted into the peer
database.
However, **live enforcement is currently INERT.** No `Authority` is ever constructed at runtime,
because the AUM-chain sync RPC family (`/machine/tka/sync/*`) that would fetch the trusted-key set
from the control plane is **not yet implemented** in this fork. `MapResponse` only carries the AUM
*head* hash and the per-peer signature to be verified — never the trusted keys to verify against —
so the trusted-key `Authority` cannot be derived from data the client already receives.
**Consequence:** until the AUM-sync RPC and chain replayer are built and an `Authority` is supplied,
**do not rely on Tailnet Lock for control-plane-compromise protection.** A malicious or compromised
control plane can inject peer node-keys and the client will accept them. The enforcement code is
present and gated; it flips on the instant an `Authority` is supplied, with no further peer-trust
changes. Tracked as deferred work in [`docs/PARITY_ROADMAP.md`](docs/PARITY_ROADMAP.md).
## peerAPI capability gap
Taildrop and ingress authorization are currently **membership-only**: any node in the tailnet is
permitted, rather than being scoped to a per-peer capability as upstream does (e.g. the
`FILE_SHARING_SEND` / ingress capabilities). This is a known gap — peer capabilities are not yet
enforced for these surfaces.
## At-rest key handling is the embedder's responsibility
Auth keys, WireGuard private keys, and ACME account keys are persisted by `ts_keys` **without
at-rest encryption or in-memory zeroization**. The library does not protect this material on disk.
Securing the state directory (filesystem permissions, full-disk or directory encryption, restricting
access to the running user) is the **embedding application's** responsibility.
## Anti-leak posture (the strong part)
The design invariant this fork is built around — **the origin IP never leaks, egress is
fail-closed, and egress is IPv4-only** — is enforced both structurally and in CI:
- The `RealDialer` trait in `ts_forwarder` is the single anti-leak chokepoint. The default
`DirectDialer` structurally refuses exit egress, so the real origin IP cannot leak by accident; the
proxy dialer is selected only when exit egress is explicitly enabled and a proxy is configured.
- Any proxy connect/handshake failure **drops the flow** — there is never a fallback to a direct
host-IP dial. UDP over the proxy fails closed. An SSRF guard rejects forbidden exit destinations
(loopback / link-local / unspecified), and proxy credentials are redacted from `Debug` output.
- The `checks` crate runs in CI and statically guards these invariants: `ipv4_only_forwarder`,
`ipv4_only_host_net`, `funnel_fail_closed`, `ssh_isolation`, and lint enablement. DNS forwarding is
routed through filters that drop any non-IPv4 upstream, so a v6 upstream can never be constructed
on the egress path.
## Reporting a Vulnerability
If you believe you have found a security vulnerability, please report it privately rather than
opening a public issue:
- Open a **private GitHub Security Advisory** at
<https://github.com/GeiserX/tailscale-rs/security/advisories/new>
(or via the repository's `Security` tab → `Report a vulnerability`).
Please include a description of the issue, the affected component(s), and steps to reproduce if
possible. We will acknowledge the report and work with you on a coordinated disclosure timeline.
## Not affiliated with Tailscale Inc.
This is an independent fork. "Tailscale" is a trademark of Tailscale Inc. This project is not
endorsed by, sponsored by, or affiliated with Tailscale Inc. WireGuard is a registered trademark of
Jason A. Donenfeld.