A simple file transfer "wormhole" using GTK4 and tailcat by Tailscale (main-branch-only mirror of https://forge.ejuarezg.com/ejuarezg/tailcat-wormhole)
README.md

Wormhole #

A small native GTK 4 desktop app for secure file sharing with Tailcat, written in Clojure with Babashka's FFI.

Wormhole calls GTK and GLib directly through babashka.ffi; there is no Java GUI toolkit or GTK wrapper. Tailcat supplies userspace WireGuard encryption, NAT traversal, and DERP fallback. No Tailscale account, administrator access, or network configuration is needed.

What it does #

  • Receive: starts tailcat recv --key=new --accept-dirs against a chosen folder and presents the resulting private capability address.
  • Send: sends one file or a recursive folder with tailcat cp. Folder uploads use a unique destination name by default so repeats cannot collide. The sender can turn that option off to preserve the original folder name after confirming it does not already exist in the drop box.
  • Keeps process arguments separate (no shell interpolation), displays Tailcat's activity, and lets you stop either operation.
  • Watches the receive directory and reports new folders and files once their sizes settle, since the Tailcat receiver CLI does not emit per-file events.
  • Always requests a fresh ephemeral receive key, even if the machine has a saved Tailcat default key.

The receive side is a recursive write-only drop box: senders cannot list the destination, read its contents, or overwrite an existing file. Supporting folder uploads lets senders create and stat directories, so they may infer whether a requested directory name already exists (Tailcat's documented --accept-dirs trade-off).

Requirements #

  • Linux or macOS with GTK 4 installed (gtk4/libgtk-4)
  • Babashka 1.13.220 or newer, using an FFI-capable dynamically linked build
  • Tailcat
  • OpenSSH scp (used by tailcat cp)

For example, Tailcat can be installed from source with Go:

go install github.com/tailscale/tailcat/cmd/tailcat@latest

If tailcat is not on PATH, point the app to it:

export TAILCAT_BIN="$HOME/go/bin/tailcat"

Run #

bb run

The window deliberately calls the private address a secret. A Tailcat address contains the pre-shared key needed to connect, so exchange it through a private channel. It becomes unusable when the ephemeral receive process stops.

OCaml port #

An OCaml/ocgtk implementation lives in ocaml/. Its local Guix and Opam setup, launch command, and test commands are in ocaml/README.md. It is developed independently of the Babashka implementation above.

Install and package on GNU Guix #

The Guix package bundles GTK 4, an FFI-capable Babashka 1.13.220, Tailcat 0.6.0, OpenSSH scp, and Mozilla's CA certificates. Install it into your current Guix profile with:

guix time-machine -C packaging/channels.scm -- \
  package --install-from-file=guix.scm
wormhole

Build a single-file AppImage instead with:

bb appimage
./dist/Tailcat-Wormhole-x86_64.AppImage

The AppImage is relocatable and can run on a Linux machine without Guix. It uses FUSE when available; otherwise use:

./dist/Tailcat-Wormhole-x86_64.AppImage --appimage-extract-and-run

AppImage limitations #

  • It is currently x86-64 Linux only, because the pinned Babashka and Tailcat binary releases are x86-64 Linux builds.
  • The current artifact is 799.4 MiB (838,210,216 bytes). It contains the complete Guix GTK dependency closure rather than relying on host libraries.
  • It needs FUSE to mount normally. The extraction command above is the fallback; on hosts where Guix's user-namespace execution engine is unavailable, set GUIX_EXECUTION_ENGINE=proot (with slower startup).
  • Its bundled scp ignores system-wide and user SSH configuration. This is intentional: Tailcat supplies the proxy command for every transfer, and it avoids OpenSSH rejecting host config files under the AppImage user namespace.
  • The generated AppImage desktop metadata is still generic and does not provide a .DirIcon; this is cosmetic but may affect how some desktop environments display it.

Flatpak Installation #

Wormhole can be built as a Flatpak for easy distribution on Linux.

Build from source #

./flatpak/build.sh

Run #

flatpak run com.ejuarezg.wormhole

See flatpak/README.md for build, validation, and permission details.

Development #

bb test
bb check
bb package    # Build the Guix package
bb appimage   # Build the standalone AppImage

The code is split into:

License #

Wormhole is available under the GNU Affero General Public License v3.0. The AppImage bundles separately licensed Babashka and Tailcat executables and includes their license notices in the package.

Security notes #

Transfers are end-to-end encrypted by Tailcat's WireGuard data plane. A public DERP relay can see connection metadata and relay encrypted packets, but not file contents. Tailcat attempts a direct UDP peer-to-peer path and uses DERP as the fallback. See Tailcat's own SECURITY.md for its complete threat model and stability policy.

Wormhole does not upload addresses or files anywhere itself and does not invoke a shell for file names or addresses.

Development disclosure #

This project has been developed with substantial assistance from large language models (LLMs), a form of probabilistic automation. Such tools can produce plausible but incorrect code or explanations; their output does not establish that behavior is correct. The human maintainer is responsible for reviewing changes and validating them with tests.

This wording follows GNOME's “Probabilistically Automated” label and its focus on describing how work was produced without treating a model as a human-like agent. For more on precise, non-anthropomorphic language, see “We Need to Talk About How We Talk About ‘AI’”.