v0.3.0 — tailnet identity binding, Serve port safety, proxy stream cap
Pre-release
Pre-release
Pre-release. The VPS test matrix in docs/VPS_TEST_RESULTS.md is still pending on both required rows, so v0.2.4 remains the recommended install until that evidence lands. This tag exists so the matrix runs have a pinned ref to test.
Upgrading breaks two things on purpose
- Every client must re-pair. Session cookies are now bound to the tailnet identity that paired them, so a cookie stolen from one tailnet user is useless to another. Cookies issued before this carry no binding and are rejected. Run
sudo pai-anywhere reset-accessand pair again. - Your tailnet URL may gain a port. Serve no longer assumes 443. If another service already owns that port on the host, install falls back to 10000 and prints the URL with it. Update your ACL grant to match, or pin one with
PAI_ANYWHERE_SERVE_PORT.
Pairing from a tagged node now returns 403, because Tailscale Serve stamps no user identity for tagged devices. See "Pairing returns 403" in docs/TAILNET_ACCESS.md for the diagnostic and the PAI_ANYWHERE_REQUIRE_TAILNET_IDENTITY=0 opt-out.
Fixes
- install.sh aborted mid-install. A comment sat between a
\line continuation and its argument, truncating therunusercall sobash -lcran with no script. Exit 127 underset -e; Claude Code never installed. - Browser pairing always returned 403. The CSRF guard compared full origins, but Serve terminates TLS and forwards plaintext, so the browser's
https://Origin never matched the rebuilthttp://request URL. Now compares host and port. - The proxy response cap capped nothing. It ran after
arrayBuffer(), so a chunked response was fully buffered before any check, and a live stream never terminated at all. Bytes are now counted through the stream. - Off-site backup always aborted. The parent-directory guard used
-perm /g-w, which find reads as/000and matches every file. Now-perm /022. - The backup unit could not create its own directory under
ProtectSystem=strict. The README creates it; the script reports the exact command if it is missing. - Tailscale Serve could black-hole an existing service. Serve intercepts tailnet traffic inside tailscaled before iptables, so claiming a port another proxy owns silently breaks it for every tailnet client while it keeps answering locally. Install now probes the host first and refuses to take an owned port.
Hardening
- Per-source pairing rate limit (
rate-limit.v2), keyed on tailnet identity only when Serve makes that header authoritative, socket address otherwise. Sec-Fetch-Site: same-siteno longer accepted — tailnet hosts share thets.netregistrable domain.- Hop-by-hop and
Authorizationheaders stripped before the Pulse upstream. /__gateway/healthzno longer names the service.- Constant-time pairing comparison over fixed-length digests.
- Live secrets excluded from backup archives;
reset-accessrequired after restore. pin-botis manual dispatch only, and its pinned URL is derived frominstall.shso the hash can never certify a different origin than the installer fetches.
Full changelog: v0.2.4...v0.3.0