Skip to content

Transports and Security

phroun edited this page Aug 22, 2026 · 1 revision

The wire language is the same whatever carries it. A unix socket, plain TCP and TLS differ only in the endpoint you dial — every page in this wiki applies unchanged over all three.

conn, err := client.Dial("tls://host:9797", "My App", nil)

Endpoints

Endpoint Transport
/run/kittytk/display-0.sock unix socket — a bare path, and the default
unix:/run/kittytk/display-0.sock the same, written explicitly
tcp://host:port plain TCP — loopback or a trusted LAN only
tls://host:port TLS — for anything else

tcp:// and tls:// assume port 9797 when none is given.

A bare path is still a unix socket, so Dial, dial and kt_dial keep the signatures they always had and existing code is unaffected.

Where the endpoint comes from

$KITTYTK_DISPLAY — read by clients and hosts alike, so the two always agree.

Unset, it defaults to $XDG_RUNTIME_DIR/kittytk/display-0.sock, falling back to the system temporary directory when XDG_RUNTIME_DIR is not set.

On Windows the default is tcp://127.0.0.1:9797 instead, because AF_UNIX is not dependable there.

tcp:// is not private

Plain TCP carries the wire language in the clear, and the wire language is your whole interface: captions, list contents, whatever a user types into a TextInput. Use it on loopback, or on a network you already trust for everything else. Otherwise use tls://.

TLS without a certificate authority

There is no CA and nothing to buy. The host presents a persistent self-signed certificate; your client records its SHA-256 fingerprint the first time it connects and checks it on every reconnect afterwards — the same trust-on-first-use model as SSH.

The pins live in known_hosts. On a first connection the client stores the fingerprint and says so:

kittytk: pinned new host host:9797 sha256:1f3a…

On a later connection to a host whose identity has changed, it refuses and tells you what to do:

host identity for host:9797 changed!
  pinned sha256:1f3a…
  got    sha256:9b02…
if this is expected, remove that line from /home/you/.config/kittytk/known_hosts

That refusal is the point of the scheme — it is what stands between you and someone else answering on that address. Do not clear the line without knowing why it changed.

The first connection is the one TOFU cannot protect. To close that gap, get the fingerprint by some other route — the host prints it at startup — and write it into known_hosts before you connect.

Your client has an identity too

tls:// is mutual: the client presents a persistent self-signed identity of its own, generated once on first use and kept in identity.pem. It is your client's long-lived key, like an SSH key, and there is nothing to configure.

The host identifies you by that fingerprint together with the application name you passed to Dial. Two consequences worth planning for:

  • A connection may not be admitted immediately. The host asks its user, and your dial waits on a human. Handle a connection that takes a while, or is refused.
  • Your application name is part of your identity. Being trusted once does not let you connect later under a different name — approval is remembered per name, so a client cannot quietly present an app it was never approved for. Pick a name and keep it.

Local connections — a unix socket, or loopback — are same-machine and are admitted without asking.

Headless

A host that has no user to ask may accept a shared token instead. Put it in $KITTYTK_TOKEN and the client presents it in the handshake. Over tls:// it cannot be sniffed; over tcp:// it can, so treat the two differently. It never applies to local connections, which do not need it.

Files

Per-user, in <config>/kittytk/, where <config> is $XDG_CONFIG_HOME, else %APPDATA% on Windows, else ~/.config. The Go, Python and C clients all use that same rule, so one machine has one store and they share it.

Path What
identity.pem your client's key and certificate
known_hosts fingerprints you have pinned

Environment

Variable Purpose
KITTYTK_DISPLAY the endpoint — path, or tcp:// / tls:// URL
KITTYTK_TOKEN handshake token, where a host requires one
KITTYTK_IDENTITY use a different client identity PEM
KITTYTK_KNOWN_HOSTS use a different pin store
KITTYTK_INSECURE 1 turns pinning off — diagnostics only

KITTYTK_INSECURE removes the protection this whole section is about. It is for working out why a handshake fails, not for getting past one.

The three clients

All of them run on Windows, Linux and macOS, and all speak the same transports.

Go — everything, everywhere.

Pythontls:// needs either the cryptography package or the openssl command line, to mint the client identity on first use; without either, point $KITTYTK_IDENTITY at a PEM you already have. AF_UNIX may be missing on Windows, so use tcp:// there.

C — builds on Winsock2 and on POSIX. The default build needs only libc and pthreads and speaks unix sockets and tcp://; tls:// is opt-in with -DKT_TLS and links OpenSSL.

See also

Protocol Overview — the language that travels over this · Application — the name you dial with · Introspection — asking a host what it supports

Clone this wiki locally