Tailkitty
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.7.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.
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.
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 browse 'tc...'
tailkitty recv ~/inbox
tailkitty cp report.pdf 'tc...':
tailkitty serve exec -- /usr/bin/fortune
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.7's SOCKS5 UDP association and native UDP forwarding primitives plus
an auditable patch that adds narrowly filtered, opt-in IPv4-loopback 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..."
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=Nonein Python preserves the upstream allow-all default.allow=[]becomes--allow=noneand denies every client.TAILKITTY_BACKENDis 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:
TAILKITTY_BACKEND, if explicitly set; an invalid path fails immediately.- The verified, runtime-compatible executable bundled in the installed wheel.
.tools/bin/tailcat, then the legacy.tools/bin/tailcat-go, in a source checkout.tailcat-goonPATH.
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
pytailcatpackage - 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.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| tailkitty-0.3.0.tar.gz | 128.2 kB | With provenance | Details for tailkitty-0.3.0.tar.gz |
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| tailkitty-0.3.0-py3-none-win_arm64.whl | Python 3 | none | Windows ARM64 | Details for tailkitty-0.3.0-py3-none-win_arm64.whl |
| tailkitty-0.3.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details for tailkitty-0.3.0-py3-none-win_amd64.whl |
| tailkitty-0.3.0-py3-none-manylinux_2_17_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details for tailkitty-0.3.0-py3-none-manylinux_2_17_x86_64.whl |
| tailkitty-0.3.0-py3-none-manylinux_2_17_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details for tailkitty-0.3.0-py3-none-manylinux_2_17_aarch64.whl |
| tailkitty-0.3.0-py3-none-macosx_12_0_x86_64.whl | Python 3 | none | macOS 12.0+ x86-64 | Details for tailkitty-0.3.0-py3-none-macosx_12_0_x86_64.whl |
| tailkitty-0.3.0-py3-none-macosx_12_0_arm64.whl | Python 3 | none | macOS 12.0+ ARM64 | Details for tailkitty-0.3.0-py3-none-macosx_12_0_arm64.whl |
Total release size: 41.2 MB
Release files / tailkitty-0.3.0.tar.gz
| Download URL | tailkitty-0.3.0.tar.gz |
|---|---|
| Size | 128.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5a2a3474f7b466bfb25bb2aa7750bdecd570e68ccddcf1a19474cb2d52f30e70
|
|
BLAKE2b-256 checksum How to use checksums |
0616ffbceaa61f0e87f70444bcfe27a91555577815d1f2a263012e5d2970ae21
|
| 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 Oct 4, 2026.
Transparency logRelease files / tailkitty-0.3.0-py3-none-win_arm64.whl
| Download URL | tailkitty-0.3.0-py3-none-win_arm64.whl |
|---|---|
| Size | 6.4 MB |
| Tags | Python 3 Windows ARM64 |
|
SHA-256 checksum How to use checksums |
1071898c679cd1d3ef587febddea24529c94bc1eb16b354f77ba82629ce9dd02
|
|
BLAKE2b-256 checksum How to use checksums |
78f33f1215370ec6bac4f4523679748f4276684009c7ada3a0168f4b3a9b5868
|
| 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 Oct 4, 2026.
Transparency logRelease files / tailkitty-0.3.0-py3-none-win_amd64.whl
| Download URL | tailkitty-0.3.0-py3-none-win_amd64.whl |
|---|---|
| Size | 7.2 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
972b05b1b89d4347d09f1e9e35eb4702e072da81713d73cb7b07d087d3791d26
|
|
BLAKE2b-256 checksum How to use checksums |
ba1623015914e3d615dfc3a9ec45ec18d8876811259f1e21c158a13330c83a83
|
| 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 Oct 4, 2026.
Transparency logRelease files / tailkitty-0.3.0-py3-none-manylinux_2_17_x86_64.whl
| Download URL | tailkitty-0.3.0-py3-none-manylinux_2_17_x86_64.whl |
|---|---|
| Size | 7.2 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
a722612bd026508ab786da1a0746f79fad6723af4395428c7f1495e115044331
|
|
BLAKE2b-256 checksum How to use checksums |
565470d870bfe5d19eafb4e60fd91a0edfbf58d79caf6a1eaa4f7fb898dcf663
|
| 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 Oct 4, 2026.
Transparency logRelease files / tailkitty-0.3.0-py3-none-manylinux_2_17_aarch64.whl
| Download URL | tailkitty-0.3.0-py3-none-manylinux_2_17_aarch64.whl |
|---|---|
| Size | 6.5 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
679ecb9a642bbc0e962270262713ab3abfbdc93c03e90aac723b553dbbcf7906
|
|
BLAKE2b-256 checksum How to use checksums |
f3d9121b6ec4f4c73bcdd99ce555cfb970793dc80e77e34191b36404d091fc14
|
| 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 Oct 4, 2026.
Transparency logRelease files / tailkitty-0.3.0-py3-none-macosx_12_0_x86_64.whl
| Download URL | tailkitty-0.3.0-py3-none-macosx_12_0_x86_64.whl |
|---|---|
| Size | 7.1 MB |
| Tags | Python 3 macOS 12.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
04390ffd9fa0aaeb226900752e175d9234e8671520c5b0fd4a7bec389c074be4
|
|
BLAKE2b-256 checksum How to use checksums |
0a785801f7f37d8bfcfb8ba22bb4a50b7f5f91316dd02e26d8e682bfacabcda3
|
| 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 Oct 4, 2026.
Transparency logRelease files / tailkitty-0.3.0-py3-none-macosx_12_0_arm64.whl
| Download URL | tailkitty-0.3.0-py3-none-macosx_12_0_arm64.whl |
|---|---|
| Size | 6.6 MB |
| Tags | Python 3 macOS 12.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
f89b4147d0b80abb7bfc908aaff6e609a6cc605b4d139807c4a3e466abe0337d
|
|
BLAKE2b-256 checksum How to use checksums |
5322d87cd59028877728d2d6382dbe7dcbaf839d7e2d71649cf28384be086fc1
|
| 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 Oct 4, 2026.
Transparency logSecurity for tailkitty 0.3.0
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.
Attestation for tailkitty-0.3.0.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 Oct 4, 2026.
Transparency logA 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.