Skip to main content

Tailkitty

PyPI Python CI

Python tooling and verified platform wheels for Tailscale Tailcat: encrypted, account-free connections over Tailscale's data plane.

Tailkitty gives Python users two things:

  • Pure-Python tools for Tailcat addresses, DNS destinations, and DERP-map resolution.
  • A pinned Tailcat v0.6.0 executable for the interoperable WireGuard, DERP, NAT-traversal, and userspace-networking data plane.

The executable in each platform wheel is checked for its target, version, size, and SHA-256 digest before Tailkitty runs it. You do not need Go, root access, a Tailscale account, or a Tailscale control server.

[!WARNING] Tailcat and Tailkitty are experimental. Their address, CLI, and Python APIs may change before a stable release.

Start here

Goal Start with
Send bytes between two computers One-shot pipe
Reach a remote web app or database Forward a port
Exchange UDP datagrams Typed UDP
Use Tailcat from Python Python quickstart
Transfer files or use SSH Recipes
Diagnose an installation Troubleshooting
Understand the trust boundary Security
Develop or package Tailkitty Contributing

Install

Install the command-line application with uv:

uv tool install tailkitty
tailkitty doctor

Add the typed Python library to a project with:

uv add tailkitty

pip install tailkitty also works. A supported platform receives a self-contained wheel with both the tailkitty command and a tailcat compatibility alias. Confirm what will run with:

tailkitty --version
tailkitty doctor
tailkitty version

These report the Tailkitty version, verified backend provenance, and upstream Tailcat version, respectively.

Supported wheels

Operating system Architecture Wheel platform tag
macOS 12+ Apple Silicon macosx_12_0_arm64
macOS 12+ Intel x86-64 macosx_12_0_x86_64
Linux, glibc 2.17+ x86-64 manylinux_2_17_x86_64
Linux, glibc 2.17+ ARM64 manylinux_2_17_aarch64
Windows x86-64 win_amd64
Windows ARM64 win_arm64

Python 3.11 and newer is supported. The Python code is ABI-independent, but the bundled executable is platform-specific. Unsupported systems can use the pure-Python control-plane features or set TAILKITTY_BACKEND to a compatible Tailcat executable.

Quickstart

Both machines need Tailkitty installed. A tc... address is case-sensitive; copy it exactly and quote it in shell commands.

One-shot pipe

On the receiving machine:

tailkitty --key=new < /dev/null
# 🐈 Server listening with new address: tc...

On the sending machine, paste the complete address:

printf 'hello from Tailkitty\n' | tailkitty --key=new 'tc...'

The receiver prints the message and both processes exit. This mode carries one finite byte stream; use port serving or Client.connect() for a long-lived connection.

PowerShell equivalents:

$null | tailkitty --key=new
'hello from Tailkitty' | tailkitty --key=new 'tc...'

Forward a port

Suppose a web application is listening on port 8080 of the server. Start Tailkitty there:

tailkitty serve 8080
# 🐈 Server listening with new address: tc...

On the client, expose it on local port 18080:

tailkitty forward 'tc...' 18080:8080

Open http://127.0.0.1:18080/. The forwarder binds to localhost by default and runs until you press Ctrl-C.

[!IMPORTANT] Anyone who has an unrestricted server address can connect. Use client keys and --allow for controlled access; do not treat WireGuard encryption as authorization. See Protect a server.

Python quickstart

On the server:

import time

from tailkitty import ServerProcess

# This allows anyone with the secret address to connect. See the security note below.
with ServerProcess(serve=8080, key="new", allow=None) as server:
    print(server.token, flush=True)
    while True:
        time.sleep(60)

On the client, after receiving the complete address:

from tailkitty import Client

response = Client("tc...").request(
    b"GET / HTTP/1.0\r\nHost: localhost\r\n\r\n",
    port=8080,
    timeout=30,
)
print(response.decode(errors="replace"))

The context manager waits for a validated address, then terminates and reaps the native process on exit. See the Python API guide for asyncio, streaming, token handling, server options, exceptions, and low-level execution.

Typed UDP

Tailkitty preserves application datagram boundaries instead of treating UDP as a byte stream. On the server, bind your UDP application to localhost and opt in to the same Tailcat port:

from tailkitty import ServerProcess

with ServerProcess(udp=5353, key="new") as server:
    print(server.token)
    input("Press Enter to stop the UDP tunnel... ")

On the client:

from tailkitty import Client

with Client("tc...").connect_udp(5353, timeout=10) as connection:
    reply = connection.request(b"one datagram", timeout=3)
    print(reply.data)

send() and receive() each handle exactly one datagram. Payloads are limited to MAX_UDP_PAYLOAD (1232 bytes), the safe size for Tailcat's tunnel MTU. Asyncio equivalents are available through AsyncClient.connect_udp().

CLI model

Tailkitty owns a small Python-native command surface:

tailkitty parse 'tc...'            # validate and decode an address as JSON
tailkitty resolve 'tc...'          # embed the referenced DERP relay details
tailkitty doctor                   # inspect backend selection and provenance
tailkitty doctor --json            # machine-readable diagnostics
tailkitty --version                # Tailkitty package version

Everything else passes unchanged to the pinned Tailcat executable:

tailkitty serve --help
tailkitty ping --until-direct 'tc...'
tailkitty forward 'tc...' 18080:8080
tailkitty recv ~/inbox
tailkitty cp report.pdf 'tc...':
tailkitty serve --ssh-authorized-keys=alice@github ssh
tailkitty ssh 'tc...'
tailkitty socks 'tc...' curl http://server.tailcat:8080/
tailkitty genkey --client --key=client-default

Run tailkitty readme for the documentation embedded in the bundled Tailcat version. Tailkitty's typed UDP API uses Tailcat v0.6's SOCKS5 UDP association and an auditable bundled patch that adds opt-in 127.0.0.1 UDP serving to the CLI.

Addresses and DNS

A Tailcat address starts with tc and contains a CBOR document encoded as unpadded base64url. A current address can contain:

  • The server's WireGuard public key.
  • A separate discovery public key.
  • A WireGuard pre-shared key.
  • Either a DERP region ID or embedded relay details.

Tailkitty parses and validates those fields in Python. Unknown future CBOR fields are retained in the extensions mappings on ConnInfo, DerpRegion, and DerpNode, preventing resolution from silently stripping new upstream fields.

Tailcat commands and Client also accept a DNS name with a TXT record like:

service.example.com. 300 IN TXT "tailcat=tc..."

[!CAUTION] DNS is public. Publishing an address removes its normal secrecy. A DNS-named server must authenticate clients independently with --allow, SSH authorized keys, or both. Never publish an unrestricted service, especially no-auth-ssh.

Short addresses refer to a DERP region by ID. tailkitty resolve expands one into a self-contained address using a bounded, validated DERP-map cache:

tailkitty parse 'tc...'
tailkitty resolve 'tc...' > full-address.txt

Python API overview

API Native backend? Purpose
parse_token(token) No Decode and validate an address into ConnInfo
resolve_token(token) No Embed DERP relay details in a short address
resolve_destination(value) No Resolve a literal address or DNS TXT destination
DerpMapCache No Bounded HTTP cache with ETag and stale fallback
Client.request(...) Yes Finite request/response exchange
Client.run(...) Yes Finite exchange with exit status and stderr
Client.connect(...) Yes Full-duplex subprocess.Popen connection
Client.connect_udp(...) Yes Datagram-preserving UDP connection
ServerProcess Yes Managed synchronous server
AsyncClient / AsyncServerProcess Yes Asyncio equivalents, including UDP
run(...) / run_async(...) Yes Low-level Tailcat command execution
diagnostics() Yes Structured backend and environment report

The distribution includes py.typed and is checked with strict mypy. Public exceptions and full examples are documented in the Python API guide.

Security summary

  • Addresses are capability material. Current addresses include a pre-shared key; do not post them in logs, screenshots, issues, or public DNS unless another authorization layer is active.
  • A server allows every client by default. Generate a client identity with tailkitty genkey --client --key=client-default, then supply its printed public key with --allow.
  • allow=None in Python preserves the upstream allow-all default. allow=[] becomes --allow=none and denies every client.
  • TAILKITTY_BACKEND is explicit code-execution authority. Tailkitty checks that the path is executable but cannot prove the provenance of a user-selected binary.
  • Bundled executables fail closed: a present but invalid bundle raises an integrity error instead of falling back to an unverified executable.

Read the security policy before exposing SSH, files, an exit node, or a DNS-named service.

How it works

Python caller or tailkitty CLI
        |
        +-- token.py -------- address codec and validation (pure Python)
        +-- destination.py -- literal or DNS TXT lookup (pure Python)
        +-- derp.py --------- validated DERP-map cache (pure Python)
        +-- client.py ------- sync and asyncio client facade
        +-- process.py ------ managed server and subprocess lifecycle
        |
        +-- backend.py ------ deterministic backend discovery
                |
                +-- verified wheel bundle
                +-- development build
                +-- explicitly configured upstream executable

The encrypted data plane stays upstream because it depends on Tailscale's Go implementations of magicsock, userspace WireGuard, DERP, and gVisor netstack. Reimplementing only part of that stack in Python would lose interoperability and security properties. See architecture guide for the detailed boundary and data flows.

Backend selection

Tailkitty selects exactly one backend in this order:

  1. TAILKITTY_BACKEND, if explicitly set; an invalid path fails immediately.
  2. The verified, runtime-compatible executable bundled in the installed wheel.
  3. .tools/bin/tailcat, then the legacy .tools/bin/tailcat-go, in a source checkout.
  4. tailcat-go on PATH.

An installed tailcat command is not searched on PATH because Tailkitty itself provides that alias and recursive execution must be avoided. Use TAILKITTY_BACKEND=/path/to/tailcat for an external upstream binary.

Development

The repository uses mise for tool versions and uv for Python environments:

mise install
mise run setup
mise run test

Useful tasks:

mise run backend          # build the pinned development backend
mise run bundle           # build the host bundle
mise run bundle-verify    # verify its manifest and executable
mise run wheel            # build the host wheel
mise run wheels           # build and verify all six wheels
mise run upstream-check   # compare the pin with the latest stable Tailcat release

Start with CONTRIBUTING.md. Packaging and release details are in BUILDING.md and RELEASING.md. Coding agents must also follow AGENTS.md.

Documentation

  • Recipes — pipes, ports, files, SSH, SOCKS, DNS, and key management
  • Python API — typed sync/async usage and behavior
  • Troubleshooting — bounded diagnostics and common failures
  • Architecture — component boundaries, data flows, and invariants
  • Security policy — threats, authorization, bundle trust, and reporting
  • Building — reproducible native and wheel builds
  • Releasing — versioning, verification, publication, and rollback constraints
  • Comparison — Tailkitty versus the separate pytailcat package
  • Changelog — user-visible release history

License and relationship to Tailscale

Tailkitty is licensed under the MIT License. Bundled wheels contain upstream Tailcat under its BSD 3-Clause License and other Go dependencies; see THIRD_PARTY_NOTICES.md.

Tailcat and Tailscale are trademarks of Tailscale Inc. Tailkitty is not an official Tailscale product.

Metadata

Release files for tailkitty 0.2.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tailkitty 0.2.3
File Size Uploaded Provenance Actions
tailkitty-0.2.3.tar.gz 125.4 kB With provenance Details for tailkitty-0.2.3.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for tailkitty 0.2.3
File Actions
tailkitty-0.2.3-py3-none-win_arm64.whl Python 3 none Windows ARM64 Details for tailkitty-0.2.3-py3-none-win_arm64.whl
tailkitty-0.2.3-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details for tailkitty-0.2.3-py3-none-win_amd64.whl
tailkitty-0.2.3-py3-none-manylinux_2_17_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details for tailkitty-0.2.3-py3-none-manylinux_2_17_x86_64.whl
tailkitty-0.2.3-py3-none-manylinux_2_17_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details for tailkitty-0.2.3-py3-none-manylinux_2_17_aarch64.whl
tailkitty-0.2.3-py3-none-macosx_12_0_x86_64.whl Python 3 none macOS 12.0+ x86-64 Details for tailkitty-0.2.3-py3-none-macosx_12_0_x86_64.whl
tailkitty-0.2.3-py3-none-macosx_12_0_arm64.whl Python 3 none macOS 12.0+ ARM64 Details for tailkitty-0.2.3-py3-none-macosx_12_0_arm64.whl

Total release size: 40.7 MB

Release files / tailkitty-0.2.3.tar.gz

Download URL tailkitty-0.2.3.tar.gz
Size 125.4 kB
Tags Source
SHA-256 checksum
How to use checksums
f1f0ee21d2565eb6f497964e1705efdfe2daf3bcd2712ee22144971b9d9adddf
BLAKE2b-256 checksum
How to use checksums
2cec6713ca25c851303b8afe9863edc0e3a7e8de49df4edf17ef000a3226d034
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release files / tailkitty-0.2.3-py3-none-win_arm64.whl

Download URL tailkitty-0.2.3-py3-none-win_arm64.whl
Size 6.3 MB
Tags Python 3 Windows ARM64
SHA-256 checksum
How to use checksums
9d7a7b30572ed0ea81f85c6da03fa4f13e65cf05c578051f32b8bc314bfe1c83
BLAKE2b-256 checksum
How to use checksums
e0cfcd7af25693a61357e954b2b3e30d9397d9de0160d85ad5fbd8677ce3b614
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release files / tailkitty-0.2.3-py3-none-win_amd64.whl

Download URL tailkitty-0.2.3-py3-none-win_amd64.whl
Size 7.1 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
0c8d850debf8e3230291eaa8f1b69a01303fc9c6447415c3e58b5a3a9f964a35
BLAKE2b-256 checksum
How to use checksums
b3bc3d4ceedbf027f59389a292cf99d67c51df064efebee6668ef0887f65992d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release files / tailkitty-0.2.3-py3-none-manylinux_2_17_x86_64.whl

Download URL tailkitty-0.2.3-py3-none-manylinux_2_17_x86_64.whl
Size 7.1 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
aa0686921cfb90a9761129992710cbcc48cf549d6e7b1e2124a547a4a9a58527
BLAKE2b-256 checksum
How to use checksums
58690d76ff8db7d05c053900dd7bfa60e8466c94c64df7c175c0b5c30b7bb116
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release files / tailkitty-0.2.3-py3-none-manylinux_2_17_aarch64.whl

Download URL tailkitty-0.2.3-py3-none-manylinux_2_17_aarch64.whl
Size 6.4 MB
Tags Linux glibc 2.17+ ARM64 Python 3
SHA-256 checksum
How to use checksums
909ac41c34ba0dc6a81e4090b7496e6e1a8305407659c4b3424fd6c7f9427419
BLAKE2b-256 checksum
How to use checksums
cfa61036ccc82ab91726124906258af1df2b8e2c2f46594fc91cc78b4bbfb460
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release files / tailkitty-0.2.3-py3-none-macosx_12_0_x86_64.whl

Download URL tailkitty-0.2.3-py3-none-macosx_12_0_x86_64.whl
Size 7.0 MB
Tags Python 3 macOS 12.0+ x86-64
SHA-256 checksum
How to use checksums
49cc57c12623bdc5ad1c52f64e1ef268e0a12e55f315b6afde3945d23c2ed51e
BLAKE2b-256 checksum
How to use checksums
e0700e8450c86040646d024968a35dcace75972cdc8373778476cbf709c02aaf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release files / tailkitty-0.2.3-py3-none-macosx_12_0_arm64.whl

Download URL tailkitty-0.2.3-py3-none-macosx_12_0_arm64.whl
Size 6.5 MB
Tags Python 3 macOS 12.0+ ARM64
SHA-256 checksum
How to use checksums
e0114e06eff81d8e3d54b9c0a77c290e39e82837de8495b6ead4220124dea7de
BLAKE2b-256 checksum
How to use checksums
5b0cc52bfe008e7492d08dd920aad849775f83cd540c87cc0fb0feffeaf496d9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Security for tailkitty 0.2.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 7 files in this release have been uploaded with provenance metadata.

Select from 7 files

Attestation for tailkitty-0.2.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page