Encrypted peer-to-peer port forwarding for machines with no open ports.
Default deny -- every port granted to exactly the peer you choose.
pai-sho forwards specific TCP ports between your machines over an encrypted, peer-to-peer QUIC connection (built on iroh). Neither machine needs an open inbound port, a public IP, or a relay you run -- iroh handles discovery, NAT traversal, and relay fallback.
Access is default deny and per peer. Each machine runs one long-lived daemon with a stable identity (a keypair). You grant a specific port to a specific peer's key; that peer, and no one else, can reach it. A machine you have not met enrolls with a one-time token -- so you can boot a fleet of untrusted workloads that phone home and each get exactly the access you granted, with no manual key exchange, and with siblings invisible to each other.
The case it was built for: a dedicated VM per task -- a
vibenv -- with no inbound ports. Boot it,
it dials your laptop, and the two or three ports you care about (a web app, a
live-reload server, a terminal) show up on localhost, reachable by you alone.
On my laptop the daemon is already running. Its ticket is stable, so I look it up once, and I mint a one-time token for the VM I'm about to boot:
pai-sho ticket
# 5hc4bjqfp6booceusm3jrfebbegyfi6aiqwbgx4xxqmpvg5usoyq
pai-sho grant-token --label vm
# 7fd25613dd5e17cb... (one-time, valid 5 minutes)The VM runs an http-nu app on :3001 and
stellar on :7331 for live CSS editing.
I start its daemon pointing home, exposing both ports to my laptop:
pai-sho daemon -a 5hc4bjqfp6booceusm3jrfebbegyfi6aiqwbgx4xxqmpvg5usoyq \
-e 3001,7331 --enroll 7fd25613dd5e17cb...The VM enrolls under the label vm, and localhost:3001 and localhost:7331 on
my laptop reach it -- and only my laptop; anyone else who dials the VM is refused.
Close the laptop, reopen it, and the connection restores on its own, no new token
needed.
Spin up something new on the VM and expose it live:
http-nu :3002 -c '{|req| "hello from a new experiment"}'
pai-sho expose 3002It's immediately at http://localhost:3002 in my browser. Done with it?
pai-sho unexpose 3002.
cargo install pai-shobrew install cablehead/tap/pai-shoeget cablehead/pai-shoOr grab a binary from releases.
pai-sho [--socket <path>] <command>
daemon [options] Start the daemon
ticket Print the daemon's ticket
grant-token --label <l> Mint a one-time enrollment token (valid 5 min)
pin <key> --label <l> Enroll a peer by key, no token (host-attested)
add-peer <ticket> Connect to a peer
remove-peer <ticket> Disconnect from a peer (and drop its pin)
expose <port> [--to <key>] Grant a local port to peers (default: all known)
unexpose <port> [--to <k>] Revoke grants for a port (or one peer's grant)
list Show peers, grants, and bindings (JSON)
| Option | Default | Description |
|---|---|---|
--host |
127.0.0.1 |
Address to forward exposed ports to |
-a, --add |
Add peer on startup (repeatable) | |
-e, --expose |
Expose port to the -a peers (repeat or comma-separate) |
|
--enroll |
One-time token to present to the -a peers |
|
--key |
~/.local/state/pai-sho/key |
Secret key path (created if missing) |
--socket |
/tmp/pai-sho.sock |
Unix socket path |
Identity. Each daemon has a stable ticket -- an iroh endpoint ID backed by a
keypair persisted at --key. Because it is stable, a launcher can bake one operator
ticket into every workload it boots.
Grants. Access is default deny. A port is exposed by a grant -- (port) -> peer key -- and served only to the peers named in one. iroh gives the connecting peer's
key cryptographically, so a grant names a proven identity, not a shareable address:
you cannot hand out reach by leaking a string
(ADR 0001).
Enrollment. An incoming connection from an unknown key is refused unless it
presents a one-time token minted by grant-token. A valid claim pins the peer's key
under the token's label and is then spent; pins persist across restarts, so a reboot
does not orphan enrolled workloads (ADR 0002).
Forwarding. Each peer is announced only the ports granted to it. When a peer
grants you port 3001, a local TCP listener binds 127.0.0.1:3001 on your side, and
traffic runs over the encrypted QUIC connection. It works both ways -- something
running locally on :4001 becomes reachable on the peer with pai-sho expose 4001.
Reconnection. If the connection drops, both sides reconnect with exponential backoff. Existing bindings stay in place and resume when the link comes back.
ngrok and Cloudflare Tunnel are great when you need a public URL anyone can reach. pai-sho is for connecting your own machines, or sharing a ticket with a friend so they can see something you're working on.
SSH tunnels need inbound access on at least one side. pai-sho works when neither machine has open inbound ports.
WireGuard, Tailscale, and NetBird are mesh VPNs that give every machine an IP on a virtual network. pai-sho is narrower: you expose specific ports, not your whole machine, which makes it easier to reason about exactly what is reachable.
dumbpipe is the direct inspiration.
