# What Are Tailcat Pre-Shared Keys? A Deep Dive into PSK Security in tailscale/tailcat

> Discover tailcat pre-shared keys, optional 256-bit WireGuard secrets that provide post-quantum confidentiality for your tunnels, securing data even from compromised relays.

- Repository: [Tailscale/tailcat](https://github.com/tailscale/tailcat)
- Tags: deep-dive
- Published: 2026-09-06

---

**Tailcat pre-shared keys are optional 256-bit WireGuard secrets that add post-quantum confidentiality to tunnels, making them unreadable even by compromised DERP relays.**

Tailcat, the peer-to-peer file transfer tool from Tailscale, implements an advanced security layer through **pre-shared keys (PSKs)**. These cryptographic secrets are mixed into every WireGuard handshake, ensuring that only the two communicating endpoints can decrypt traffic. This article examines how PSKs work in the `tailscale/tailcat` codebase, including generation, encoding, and practical configuration.

---

## The Role of Pre-Shared Keys in Tailcat

A tailcat pre-shared key serves three critical functions in the tunnel establishment process:

- **Endpoint-only secrecy**: The PSK is never transmitted over the wire; both peers must possess it independently.
- **Post-quantum protection**: Even if a DERP relay observes public keys, it cannot join or decrypt the tunnel without the 256-bit PSK.
- **Address secrecy**: When a non-zero PSK is present, the entire tailcat address becomes a secret because it embeds the key.

According to the source code in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go), the PSK is implemented as a 32-byte value using the constant `device.NoisePresharedKeySize` from the WireGuard device package.

---

## How Tailcat Generates Pre-Shared Keys

The `NewPresharedKey()` function in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (`L210-L218`) creates cryptographically secure PSKs:

```go
// tailcat.go L210-L218
func NewPresharedKey() PresharedKey {
    var psk [32]byte
    for {
        if _, err := rand.Read(psk[:]); err != nil {
            panic(err)
        }
        // Retry until non-zero (zero value disables PSK layer)
        if psk != [32]byte{} {
            break
        }
    }
    return PresharedKey(psk)
}

```

The retry loop ensures no accidentally zero-valued keys, which would silently disable the security layer.

---

## PSK Encoding and Wire Format

Tailcat uses **CBOR/JSON** for PSK serialization. The `PresharedKey` type implements custom marshalling methods in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (`L247-L255`):

```go
// Encode to text: "psk:<hex-encoded-32-bytes>"
func (p PresharedKey) MarshalText() ([]byte, error) {
    return []byte("psk:" + hex.EncodeToString(p[:])), nil
}

// Decode from text format
func (p *PresharedKey) UnmarshalText(text []byte) error {
    const prefix = "psk:"
    if !bytes.HasPrefix(text, []byte(prefix)) {
        return fmt.Errorf("invalid PSK format")
    }
    // ... hex decoding logic
}

```

The `"psk:"` prefix makes PSKs self-identifying in serialized addresses and wire messages. The [`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go) file defines how PSKs travel between peers via the `ConnInfo` structure's `PresharedKey *PresharedKey` field.

---

## Zero-Value Semantics: Disabling PSKs

A zero-valued `PresharedKey{}` disables the pre-shared-key layer entirely. As noted in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (`L163-L169`), this exists **only for backward compatibility**:

```go
// tailcat.go L163-L169
// A zero PresharedKey disables the extra secrecy layer.
// This produces shorter addresses but is NOT recommended.
// Kept only for compatibility with tailcat v0.5.0 and earlier.

```

When disabled, addresses become shorter and shareable more easily, but **security degrades significantly**—a malicious DERP operator could potentially intercept traffic. The source explicitly warns against this in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (`L414-L417`).

---

## Peer Configuration and WireGuard Integration

When the server initializes peer connections, non-zero PSKs are injected into WireGuard's peer configuration. From [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (`L1349`):

```go
// tailcat.go L1349
cfg := &wgtypes.PeerConfig{
    PublicKey:    peerKey,
    PresharedKey: device.NoisePresharedKey(b.presharedKey),
    // ... endpoint, allowed IPs
}

```

This binds the 256-bit tailcat PSK directly into the Noise protocol handshake, mixing it with the ephemeral key exchange.

---

## CLI Control: Enabling and Disabling PSKs

The [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go) file (`L105`) exposes PSK control via the `--psk` flag:

```bash

# Default: include PSK in address

tailcat serve --psk

# Compatibility mode: shorter address, no PSK

tailcat serve --psk=false

```

Programmatic usage in Go:

```go
// Server with automatic PSK generation
srv := &tailcat.Server{
    Key:          myNodeKey,
    PresharedKey: tailcat.NewPresharedKey(), // recommended
}

// Server without PSK (discouraged)
srv := &tailcat.Server{
    Key:               myNodeKey,
    DisablePresharedKey: true, // zero value used
}

```

---

## Verifying PSK Presence in Code

The `IsZero()` method checks whether a PSK is configured:

```go
psk := tailcat.NewPresharedKey()

if psk.IsZero() {
    log.Println("WARNING: PSK disabled—tunnel vulnerable to DERP compromise")
} else {
    encoded, _ := psk.MarshalText()
    log.Printf("PSK active: %s...", string(encoded)[:16])
}

```

---

## Summary

- **Tailcat pre-shared keys are 256-bit WireGuard secrets** that add post-quantum confidentiality to peer-to-peer tunnels.
- **Generation**: `NewPresharedKey()` in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) creates secure random values, rejecting all-zero results.
- **Encoding**: PSKs serialize as `"psk:<hex>"` via `MarshalText`/`UnmarshalText` for CBOR/JSON wire transfer.
- **Zero value**: Disables the security layer; retained only for v0.5.0 client compatibility.
- **CLI**: Controlled via `--psk` flag in [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go).
- **Security recommendation**: Always enable PSKs; disabling exposes tunnels to malicious relay operators.

---

## Frequently Asked Questions

### What happens if a tailcat PSK is compromised?

If a pre-shared key leaks, an attacker with the key and access to the DERP relay could theoretically decrypt traffic. However, the attacker would still need to intercept the handshake timing. Rotate keys by generating a new server with `NewPresharedKey()` and redistributing the new address.

### Why would anyone disable PSKs with `--psk=false`?

Disabling PSKs produces shorter, more shareable addresses. The `tailscale/tailcat` source explicitly warns this is **only for compatibility with tailcat v0.5.0 and earlier**. Modern clients should always use PSKs for post-quantum protection against relay compromise.

### How do I extract the PSK from a running tailcat server?

The PSK is embedded in the serialized server address. Parse the address string and locate the `"psk:"` component, then use `PresharedKey.UnmarshalText()`. Direct field access requires the `*tailcat.Server` pointer if you control the server instance.

### Does tailcat PSK affect performance?

No measurable impact. The 256-bit PSK is mixed cryptographically into the WireGuard handshake, not used for payload encryption. The handshake occurs once per session; subsequent data transfer uses standard WireGuard ChaCha20-Poly1305 at full speed.