# How Tailcat’s `--allow` Flag Controls Server Access Control

> Learn how Tailcat's --allow flag controls server access by restricting client identity keys. Understand how to allow all, deny all, or specify public keys for secure connections.

- Repository: [Tailscale/tailcat](https://github.com/tailscale/tailcat)
- Tags: how-to-guide
- Published: 2026-08-30

---

**The `--allow` flag restricts which client identity keys may complete the WireGuard handshake with a Tailcat server, supporting an empty string (allow all), `none` (deny all), or a comma-separated list of specific public keys.**

Tailcat operates in two modes: as a server listening for incoming connections or as a client dialing outbound. When running as a server, the `--allow` flag provides a lightweight, public-key-based access control list (ACL) that filters incoming WireGuard handshakes before a tunnel is established. This mechanism is implemented directly in the low-level networking backend to reject unauthorized clients at the earliest possible stage.

## Understanding the `--allow` Flag Syntax

The flag accepts three distinct value types that determine server accessibility:

- **Empty string (`""`)**: The default behavior permits all clients to connect.
- **`none`**: Blocks every client; the server ignores all incoming handshakes.
- **Comma-separated public keys**: Only clients possessing the listed `key.NodePublic` values may establish a connection.

## Parsing and Storing Allowed Client Keys

In [`main/cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/main/cmd/tailcat/tailcat.go), the flag is defined with its default and description:

```go
flagAllow = flag.String("allow", "", "comma-separated list of public keys to allow access to the server, or 'none' to allow no clients. If empty, all clients are allowed.")

```

When the server initializes via the `server()` function, the flag value is split on commas and processed sequentially:

```go
if *flagAllow != "" {
    for _, ks := range strings.Split(*flagAllow, ",") {
        if ks == "none" {
            s.AddAllowedClient(key.NodePublic{}) // empty key → deny everybody
            continue
        }
        var k key.NodePublic
        if err := k.UnmarshalText([]byte(ks)); err != nil {
            log.Fatalf("invalid key %q in --allow: %v", ks, err)
        }
        s.AddAllowedClient(k)
    }
}

```

Each valid public key string is unmarshaled into a `tailscale.com/types/key.NodePublic` struct. The `Server.AddAllowedClient` method (located in [`main/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/main/tailcat.go), lines 78-91) records these keys in an internal allow-list stored within the server's `locoBackend` instance. Invalid key formats trigger an immediate fatal error during startup, preventing the server from launching with malformed ACLs.

## Runtime Enforcement During the WireGuard Handshake

The actual access control enforcement occurs in the low-level backend when processing the initial "Meow" handshake from a client. Before adding the client to the WireGuard peer set, the backend checks the `allowedClients` map:

```go
// in tailcat.go, around line 1355
if b.allowedClients != nil && !b.allowedClients[src] {
    b.logf("ignoring meow from %v: not in allowedClients", src.String())
    // no peer is added → the client never gets a response
    return nil
}

```

The `b.allowedClients` field is a `map[key.NodePublic]bool`. If the map is **nil** (indicating no `--allow` flag was provided), the check is skipped and all clients are accepted. If the map is populated but the incoming client's public key (`src`) is absent, the handshake is silently ignored. This prevents the unauthorized client from receiving any response, effectively blocking tunnel establishment without revealing server configuration details.

## Practical Usage Examples

Generate a client identity key to use with the `--allow` flag:

```sh
$ tailcat genkey --client

# wrote file to ~/.config/tailcat/keys/client-default.private.json

nodekey:cfb6bf...ddfd16   # <-- public key to use with --allow

```

Start a server restricted to a specific client:

```sh
$ tailcat --serve=22 --allow=nodekey:cfb6bf...ddfd16

# 🐈 Server listening with saved key "default": tcXYZ...

```

Attempts from non-whitelisted clients will fail silently; the server logs the rejection while the client times out waiting for a handshake response.

Block all client connections for maintenance or debugging:

```sh
$ tailcat --serve=all --allow=none

# → only the server itself can initiate a handshake; no client will ever connect.

```

## Summary

- **The `--allow` flag** implements a public-key whitelist for Tailcat servers, parsed at startup in [`main/cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/main/cmd/tailcat/tailcat.go).
- **Three modes** are supported: allow-all (default), deny-all (`none`), or specific key enumeration.
- **Storage** occurs via `Server.AddAllowedClient`, which populates `locoBackend.allowedClients` with `key.NodePublic` values.
- **Enforcement** happens during the WireGuard "Meow" handshake in [`main/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/main/tailcat.go) (line ~1355), where unauthorized keys are silently dropped before peer registration.

## Frequently Asked Questions

### What happens if I provide an invalid public key to `--allow`?

The server will immediately exit with a fatal error during startup. The parsing logic in [`main/cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/main/cmd/tailcat/tailcat.go) calls `key.NodePublic.UnmarshalText()` on each key, and any failure prints `invalid key %q in --allow` before terminating the process.

### Can I update the allowed client list without restarting the server?

No. The `--allow` flag is parsed only once during server initialization in the `server()` function. Changes to the ACL require a server restart to re-parse the flag values and rebuild the `allowedClients` map.

### Why does the server silently ignore unauthorized clients instead of returning an error?

Silently dropping the handshake in [`main/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/main/tailcat.go) (line ~1355) is a security design choice. By returning `nil` without adding the peer, the server avoids revealing its existence or configuration to potential attackers scanning for Tailcat endpoints. The client simply sees a timeout while the server logs `ignoring meow` internally.

### What is the difference between `--allow=""` and `--allow=none`?

An empty string leaves the `allowedClients` map as `nil`, causing the enforcement check to be skipped entirely so all clients connect. The `none` value explicitly inserts an empty `key.NodePublic` into the map, creating a non-nil map that causes the enforcement logic to reject every incoming public key, including valid ones.