← blog

The two-tailnet trap: a stale key that edited the wrong network

September 27, 2026 · 6 min read

Customers reach their leased Mac over Tailscale. We keep the SSH access rule in the tailnet policy, and we manage that rule from code. When a lease starts, the customer's address is added. When it ends, it is removed. It is a small, boring system. Then it told us every customer had lost access, and it was lying.

What the tool reported

A routine check announced that the customer SSH rule was empty. Three paying customers, it said, could no longer connect. That is a five-alarm message for a business whose product is access to a machine.

The rule was not empty. We were reading a different tailnet.

Two networks, one alias

The account had two tailnets. One holds the fleet: the leased Macs, the hub, every customer share. The other was an old personal network left over from setup, holding a dead machine and a laptop.

The Tailscale API lets you name a tailnet, or pass -, which means "whichever tailnet the calling key owns". Our code passed -. That is fine until there are two keys. The keys are indistinguishable strings, so - silently follows whichever one is in the environment. A stale key had crept into one config file.

The dangerous part was the write

A read that lies wastes an afternoon. A write that lies causes an incident. The same tool has a repair mode that reconciles the access rule to the current customers. Run against the wrong tailnet, it faithfully wrote three real customer email addresses into the old network's policy, where they did nothing at all.

No customer was actually affected, because the fleet tailnet was untouched and correct the whole time. But for a few minutes the automation was editing access control on the wrong network and reporting success.

The fix: name the network

We stopped passing -. The code now names the fleet tailnet explicitly. A key that cannot see it fails loudly instead of drifting onto whatever it can see.

Every run now prints the tailnet it talked to. If that line is not the fleet, the results below it mean nothing, and you know before you act on them.

The general lesson

A convenience default that means "whatever this credential points at" is a trap the moment you have more than one credential. It turns a wrong key from an error into a silent redirection. Anything that writes should name its target, so that using the wrong credential fails instead of succeeding somewhere you did not mean.

This is the same shape as the other quiet failures we have written up in checks that cannot fail. The tool was confident. The tool was wrong. And nothing it printed would have told you, until it named the network it was editing.

Questions

How does a Tailscale API key choose which tailnet it edits?
The key belongs to one tailnet, and the API's tailnet name '-' means 'whichever tailnet this key owns'. Two keys for two tailnets look identical, so '-' silently follows the key.
How do you tell two tailnets apart safely?
Never by eye. Compare which devices each key can list. The right key sees your fleet; the wrong one sees whatever else that account owns.
Should tooling ever use the '-' tailnet alias?
Not for anything that writes. Name the tailnet explicitly so a key for the wrong network fails with a 404 instead of quietly editing the wrong ACL.

Run your own numbers on the calculator or lease a runner.