The string is the account

1 month ago

One machine prints this short string. A friend in another country pastes it into the same program, and the two machines are talking. This is tailcat. You never signed in to anything.

Ask

Ask about this presentation

Answers are generated from this presentation.

Chapters

  1. 0:00tailcat · one short string is the whole connection
  2. 0:09One side listens, the other side pastes
  3. 0:23Something has to be holding the address book
  4. 0:51The string is the account
  5. 1:29Two messages, and the machines try for a direct path
  6. 2:05The first messages always go through a relay
  7. 2:23One command, and it changes nothing on your machine
  8. 2:47The whole address book is a key and a place to meet
Show transcript

tailcat · one short string is the whole connection

tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu
the project’s own cat mark

tailcat

one short string is the whole connection
GoBSD-3-Clauseno accountno admin rights
README.md L64 · tailscale/tailcat @ c04c5af

One machine prints this short string. A friend in another country pastes it into the same program, and the two machines are talking. This is tailcat. You never signed in to anything.

One side listens, the other side pastes

$ tailcat
# Selected bootstrap relay region 302, San Francisco
# 🐈 Server listening with new address:
tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu
(hangs, waiting...)
$ tailcat
# Selected bootstrap relay region 302, San Francisco
# 🐈 Server listening with new address:
tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu
hello
$
$ echo hello | tailcat
tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu
$
pipe text · forward a port · open a shell on Linux and macOS · move a file
the same job as netcat, the thirty year old tool for shoving data from one machine to another you can already reach
README.md L60-82

One program runs on both machines. The first machine prints the string and waits. On the second machine, you hand that same string to tailcat along with a line of text. The text comes out on the first machine. Those two machines are on different networks in different countries.

Something has to be holding the address book

your machine
your router · one address for the whole house
←
the address book: where is the other machine, and which key is its key
→
your friend’s machine
your router · one address for the whole house
What is in the string?
the connection problem the project’s own bootstrap solves · tailscale/tailcat @ c04c5af

Your machine at home sits behind a router with one address for the whole house, and so does your friend’s. The two machines have no path to each other. Tailscale makes a service that joins your own machines into one private network. The service works because a server Tailscale runs holds the address book: where every machine is, and which key belongs to it. Tailcat is Tailscale’s own program, built from the same code with the account taken out. What is the other machine actually told?

The string is the account

a public key and a meeting place · 58 characters in, 42 bytes out
tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu
"ServerPublic": "nodekey:9c8d2e67…610f2e34"
"RegionID": 302
the key
the meeting place
“The short CBOR field names are the wire format: do not change or reuse them.”
type wireConnInfo struct {
ServerPublic NodePublic `cbor:"p"`
Region []*wireRegion `cbor:"r,omitempty"`
RegionID int `cbor:"i,omitempty"`
}
42 bytes
what that string decodes to · 32 of them are the key
58 characters in · 42 bytes out
README.md L159-163 · wire.go L16-19, L26-30

Here is the part worth knowing. One command opens the string up and prints everything inside, without connecting to anything. Two things come out. A public key. That is the half of a key pair you are allowed to hand out, because it only lets someone start an encrypted conversation with you. And a meeting place, written as a single number. The source code says it too. The piece that decides what a string like this may contain has exactly three fields, and two of them are the key and the meeting place. A comment above those fields says never to change them. The whole string is fifty eight characters, and it unpacks to forty two bytes. Thirty two of those forty two bytes are the key.

Two messages, and the machines try for a direct path

the machine you pasted on
Meow →
the meeting place
the meeting place
← Meowed
the listening machine
an encrypted tunnel, machine to machine
both sides swap addresses
direct
what the tool prints
pong in 42.1ms via DERP(sfo)
pong in 1.2ms via 203.0.113.7:41641
tailcat.go L1240, L1647, L417 · README.md L130-131

Both machines now know the same two things. The machine you pasted the string on goes to the meeting place and sends one message carrying its own public key. The code calls that message a Meow. The listening machine takes the sender’s key, adds it to the machines it will talk to, and answers Meowed. The two machines then build an encrypted tunnel, using WireGuard, the encryption scheme Tailscale’s products are built on. Each machine tells the other every address it thinks it has, and the two try to reach each other directly. When that works, the traffic leaves the meeting place and goes straight across. Tailcat prints both paths. The first reply came back through the meeting place. The second came straight from the other machine.

The first messages always go through a relay

and the tunnel stays encrypted through the relay
301 · nycNew York City
302 · sfoSan Francisco
303 · fraFrankfurt
304 · tokTokyo
the string can name a relay you run yourself instead
tailcat.dev/derpmap.json, fetched 2026-08-27 · tailscale.github.io/tailcat, captured 2026-08-27

That meeting place is a relay, a computer with an address both machines can reach, and its only job is to pass those first messages along. By default that relay is one of four that Tailscale runs. New York, San Francisco, Frankfurt, and Tokyo. The string can name a relay you run yourself instead. One side can even be a browser tab, and that tab stays on the relay.

One command, and it changes nothing on your machine

no tagged version yet · the install line takes the newest code
with Go
go install github.com/tailscale/tailcat/cmd/tailcat@latest
with Nix flakes
nix run github:tailscale/tailcat
GoBSD-3-Clause
fresh key each run · the address dies with the program
saved key · the same address tomorrow
--allow names the client key that may connect
README.md L45-53 · in-tree LICENSE

One command installs tailcat, and it is open source under the BSD three clause licence. It never asks for an administrator’s password, and it leaves your network settings alone. Every run makes a brand new key in memory and prints an address that has never existed. Stop the program and that address is gone. Save a key, and the same address works tomorrow. You can also name the one machine allowed to connect. Anyone else gets no answer at all.

The whole address book is a key and a place to meet

tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu
the key
the meeting place
github.com/tailscale/tailcat
GoBSD-3-Clause

Two machines on opposite sides of the internet need something holding the address book. Tailcat hands the entire thing to you as a string you can open and read. That is tailscale slash tailcat, on GitHub. New repo tomorrow.