The Problem

Running ad‑hoc, encrypted point‑to‑point streams normally requires a VPN control plane (e.g., Tailscale) or root privileges to manipulate routing. When only a lightweight “netcat‑like” tunnel is needed, provisioning a full control plane is overkill and can be a security policy obstacle.

What This Does

tailcat re‑uses Tailscale’s magicsock data‑plane (WireGuard‑encrypted UDP + DERP fallback) without any Tailscale control service. The CLI (cmd/tailcat/tailcat.go) starts a listener that prints a short, base‑64 token. The peer supplies that token to the client side (cmd/tailcat/tailcat.gotailcat.Dial) and the two processes exchange traffic over the magicsock tunnel.

  • Library entry pointstailcat.go implements the core Listener and Client types; wire.go creates the underlying magicsock connection and encodes the token.
  • Web democmd/tailcat-web/tailcat-web.go builds a WASM bundle (web/main_js.go) and serves a single‑page app (web/index.html, web/app.js). The demo uses the same library, but all traffic stays on DERP because browsers cannot open UDP sockets directly yet.

The repository also ships a minimal SSH server (cmd/tailcat/ssh.go) that runs completely in userspace, and a “serve” mode that forwards arbitrary local TCP ports (--serve=8080,8443).

How It Is Wired

  1. CLI startgo run cmd/tailcat/tailcat.go (or the installed binary) parses flags in tailcat.go.
  2. Listener pathtailcat.NewListener (in tailcat.go) creates a wire.Conn via wire.New (in wire.go). wire.New builds a magicsock.Conn (imported from Tailscale) and registers the connection with the DERP map (pickregion.go selects a bootstrap region).
  3. Token generationwire.Conn.Token() returns a short string that encodes the magicsock public key and DERP region; the token is printed by the CLI.
  4. Client path – The peer calls tailcat.Dial(token) (also in tailcat.go). The token is parsed, a matching wire.Conn is created, and magicsock.Dial contacts the selected DERP relay. If NAT traversal succeeds, magicsock upgrades to a direct UDP path; otherwise traffic continues over DERP.
  5. Data forwarding – Once the wire.Conn is established, tailcat.go copies data between the connection and stdin/stdout (or a local TCP listener when --serve is used). The copy loop is a single io.Copy per direction, so the blast radius is limited to the wire.Conn implementation.
  6. SSH modecmd/tailcat/ssh.go builds on the same listener/client flow but spawns an in‑process SSH server (ssh_stub.go/tailcat_ssh.go) that reads/writes from the wire.Conn. No external sshd is required.
  7. Web democmd/tailcat-web/tailcat-web.go runs an HTTP server that serves the static files in web/. The WASM module (web/main_js.go → compiled by go build -o webdemo/main.wasm) links the same library, calling tailcat.Dial from JavaScript via syscall/js. All traffic goes through DERP because the browser cannot use magicsock’s UDP path.

Responsibility map

File / DirPrimary Role
cmd/tailcat/tailcat.goCLI flag handling, entry point for server/client
tailcat.goPublic Listener, Dial, token encoding/decoding
wire.gomagicsock connection wrapper, DERP bootstrap, token I/O
pickregion.go / pickregion_js.goChoose DERP region (CLI vs. WASM)
cmd/tailcat/ssh.go + ssh_stub.goIn‑process SSH server implementation
cmd/tailcat-web/*.goBuild and serve WASM demo
web/*Static HTML/JS for the demo
internal/wasmbuild/wasmbuild.goHelper for go:embed of the WASM binary

The only hub in the call graph is wire.New, which is invoked by every entry point that needs a network tunnel. No circular dependencies are present; the library is deliberately flat to keep changes isolated.

How To Use It

# Clone the repo
git clone https://github.com/moses-y/tailcat
cd tailcat

# Build the CLI (requires Go 1.22+)
go install ./cmd/tailcat

# Simple pipe between two machines
# Machine A (server)
tailcat
# → prints token, e.g. tcomFwW...

# Machine B (client)
echo hello | tailcat tcomFwW...
# Server now prints "hello"

Port forwarding:

# Server: expose local 8080
tailcat --serve=8080
# Client: connect to remote token on port 8080
tailcat <token> 8080

Auth‑free SSH:

# Server
tailcat --serve=no-auth-ssh
# Client
tailcat ssh <token>
tailcat ssh <token> ls -la

Web demo (no build needed; hosted at https://tailscale.github.io/tailcat/):

# Run locally for development
go run ./cmd/tailcat-web
# Open http://localhost:8080 in a browser

All commands are documented verbatim in README.md; no extra environment variables are required beyond optional DERP_MAP_URL to point at a custom DERP map.

Real‑World Use

A security‑conscious ops team can replace temporary ssh -L tunnels with tailcat. A CI runner on an isolated subnet runs tailcat --serve=22 to expose its SSH daemon to a remote build orchestrator without opening firewall ports or installing a VPN client. The orchestrator connects via the token, establishing a WireGuard‑encrypted channel that falls back to DERP if direct UDP fails.

Code Health & Issues

Static inspection finds:

  • Low – No license fileLICENSE exists, but the repository is a fork; ensure the upstream license header matches the fork’s intent.
  • Low – Limited CI coverage – GitHub Actions run go test ./... but do not lint or enforce code coverage thresholds.
  • Low – Single DERP map hard‑coded – Default DERP map lives at https://tailcat.dev/derpmap.json; custom maps require manual flag changes, no config file is provided.

No structural red flags (unused files, circular imports, missing tests) are evident from the directory layout.

The Bottom Line

tailcat delivers a minimal, user‑space tunnel that leverages Tailscale’s proven data‑plane without any control‑plane dependencies. It is well‑structured, has a clear library/CLI split, and includes end‑to‑end tests. The main limitation is reliance on DERP for browser use and the absence of built‑in monitoring or policy enforcement, so it fits best for ad‑hoc, low‑risk data transfer or as a building block for internal tooling.