One route on a non-via router and a via pair through crashes and
returns, the via group down, expiry, approval loss, withdraw,
re-advertise and delete; route owner and peers per viewer per step.
Ephemeral devices through logout, deletes, re-auth and collection;
expired routers and nodes, a client's own expiry and logout; a login
waiting across restarts, replayed exchange by exchange.
WithLoginFlags and WithEndpoints shape what a client sends; RotateDisco
logs in again on a new disco key and leaves an auth URL waiting;
SetExpirySooner asks for an earlier key expiry.
OAuth clients, tokens, keys and tag writes by principal, and policy
writes over a baseline, replayed through v1 and v2; a deleted parent
client's key joins real clients.
SaaS answers for unknown users, a short name and a bare "@" at every
site. Each capture also runs under headscale-only user rows, at boot and
runtime; SaaS nodes must not change and overlay nodes get nothing.
SaaS answers for sshTests sources, tests semantics, failure reports,
duplicate destinations, empty and allow-all policies, and refusals.
ACL and SSH compat checks are keyed per check; differences listed.
The ephemeral garbage collector was only armed when a long-poll
session ended, or for every ephemeral node at startup. A client that
registered and went away before its first long-poll reached Connect,
for example a pod killed right after login, never ended a session,
so no deletion timer was armed. The node then stayed until headscale
restarted, well past node.ephemeral.inactivity_timeout.
Schedule the GC right after the auth-key registration, before the
error check: a failed policy refresh still returns the saved node. The
existing Cancel in serveLongPoll stops the timer once the node
connects. A timer that fires while a session is live is still skipped
by the ActiveSessions check.
TestEphemeralGCDoesNotDeleteReconnectedNode now checks that
registration arms the timer and cancels it before replaying its
sessions. Otherwise the 50ms timer could fire while the test's Connect
was still waiting for a NodeStore flush.
TestFailedReconnectDoesNotCancelEphemeralGC and
TestDeleteNodeCancelsEphemeralGC ran with a zero inactivity timeout,
so the registration timer deleted the node at once, and the delete
cancelled the timer the tests armed themselves. They now use 5s, which
outlasts the tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IPAllocator.Next took the allocator mutex once per address family.
When the IPv6 allocation failed, the IPv4 address had already been
added to usedIPs and prev4 had moved past it, so the address stayed
reserved until headscale restarted even though no node held it.
Allocate both families under a single critical section, and on an
IPv6 failure remove the IPv4 address from usedIPs and restore prev4.
An ACL compiles to one grant per destination, and each resolved and
merged the same sources again. A wide CI-style ACL took 3s and 674MB
per compile; 88ms and 23MB now.
The lock is taken before any credential check, so client-chosen machine
keys grew the map forever. Ref-count entries; delete at zero.
Fixes ANT-2026-ZC18AF82
One verdict per session, so one waiter: the newest. A displaced one
returns at once (register error, SSH re-delegate), not at session end.
A waiter keeps only the follow-up URL and the cached registration.
Fixes ANT-2026-NM50ZP4N
The rest arrives with the first MapRequest. A relogin keeps the node's
current Hostinfo, endpoints and disco key; an oversized merge is a 400.
Fixes ANT-2026-RQS7JAPP
Rows stored before the bounds are trimmed the same way at load; a
Hostinfo still over budget is dropped until the node reports again.
Fixes ANT-2026-RQS7JAPP
Fixes ANT-2026-5B6KE87K
Fixes ANT-2026-KFCFFM34
Keep the first 256 endpoints and 4096 routes; refuse longer hostnames,
too many tags, zoned endpoints and >64 KiB NetInfo or other Hostinfo.
The body limit counted wire bytes, not what NodeStore and peers keep.
Fixes ANT-2026-RQS7JAPP
Fixes ANT-2026-5B6KE87K
Fixes ANT-2026-KFCFFM34
handleRegister, the OIDC auth path and SetNodeExpiry refuse it. Expiries
are stored in UTC, which SQLite can read back, and loaded in UTC, as
PostgreSQL returns the server's zone. The v1 API answers 400.
Fixes ANT-2026-QMCAFJ1Q
json.Marshal refuses a year outside [0,9999], so one stored expiry
failed every map with that node. TailNode and key patches send UTC, the
builder drops what fails and the boot scan names the node.
Fixes ANT-2026-QMCAFJ1Q
Only a key that speaks for a tagged node re-authenticates it: a tagged
key, or one of the user it was tagged from. Another user's key or login
adds that user's own device, or only wakes the node under its node key.
Fixes ANT-2026-AWGRB7O1
Such a route names a node, not a subnet: it stays approved and listed
but no viewer gets it. Via inclusions skip the election, so the rule
sits on everything RoutesForPeer returns.
Fixes ANT-2026-MLP8ZR5T
Expired, live, ephemeral and admin-tagged devices logging in again with
spent, revoked, valid and other users' keys, by key and by login;
tag ownership through tags per policy function.
Updates ANT-2026-LYVGXVSW
Updates ANT-2026-AWGRB7O1