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-dirsagainst 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
defaultkey.
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 bytailcat 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
scpignores 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:
src/wormhole/gtk.clj— GTK/GLib FFI declarations and main-thread/file-dialog helpers.src/wormhole/tailcat.clj— safe Tailcat process and address handling.src/wormhole/main.clj— application state and UI.
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’”.