An outbound connection, stitched into a return path.
Backstitch exposes a fixed loopback TCP service on one machine through a loopback listener on another. The service-hosting machine initiates an authenticated, encrypted carrier such as an SSH remote command. Two Rust processes exchange framed TCP streams through that carrier's stdin/stdout.
protected machine hub
Backstitch agent -- outbound carrier --> Backstitch relay
| ^
v |
loopback service loopback TCP client
Version 0.2 adds per-stream flow control, heartbeats, idle deadlines, optional reconnection with bounded backoff, and clean SIGINT/SIGTERM handling. It is intended for authorized self-hosted deployments, not as a replacement for a VPN or as an independent security boundary. It has not undergone an external security audit.
Rust 1.85 or newer is required. The toolchain file selects stable Rust and
Cargo.lock pins dependencies.
cargo build --release --locked
cargo test --lockedBuild a native binary for each machine's OS and architecture. A macOS binary does
not run on Linux. Install binaries at a user-owned executable path, for example
~/.local/bin/backstitch. Both ends must use wire protocol v2; v0.1 peers are not
compatible with v0.2.
The service machine must already be able to authenticate to peer. Assume its
local SSH service listens on 127.0.0.1:22 and both machines have Backstitch:
backstitch agent --target 127.0.0.1:22 --reconnect -- \
ssh -T \
-o BatchMode=yes \
-o StrictHostKeyChecking=yes \
-o ServerAliveInterval=15 \
-o ServerAliveCountMax=3 \
peer '~/.local/bin/backstitch relay --listen 127.0.0.1:2222'On the peer:
ssh -p 2222 service-user@127.0.0.1Independently verify the service machine's SSH host key before enrolling the new address. Its normal SSH key authentication still applies. A new connection creates a new login; attaching to an existing terminal requires a terminal multiplexer separately.
The relay remains loopback-only. Other authorized peers reach it through the hub's authenticated SSH service, without exposing a new LAN listening port:
ssh -J hub-user@peer -p 2222 service-user@127.0.0.1Verify both the hub's and the service machine's host keys. Use distinct SSH
aliases and HostKeyAlias entries when several hubs expose different machines
at the same loopback address. The hub must authorize these users and the relevant
SSH jump operation. Do not change shared access policies just to make a route work.
Backstitch does not implement HTTP, TLS, WebSocket, or SSH. It launches an
argv-based carrier command without a local shell. Configure the carrier outside
this repository. An existing TLS-verified WSS transport to a hub's SSH service
can be used by SSH's ProxyCommand, then the ordinary remote command above:
agent -> verified WSS -> hub SSH -> remote-command relay
This uses permission to execute the relay; it does not request SSH reverse forwarding or a WebSocket server's reverse-tunnel operation. Use it only where this application-level return path is authorized. Backstitch does not change firewalls, proxy allowlists, or system security settings.
The carrier must provide confidentiality, authenticated peers, integrity, and an ordered duplex stream. Relay stdout is exclusively binary protocol; diagnostics use stderr. Do not allocate a PTY or allow banners on stdout. Never check in keys, tokens, private endpoints, or credential-bearing proxy commands.
| Option | Default | Meaning |
|---|---|---|
--max-connections |
64 | Maximum connections per session, 1..1024. |
--connect-timeout |
10 s | Handshake and target connection deadline. |
--heartbeat-interval |
15 s | PING cadence, even when TCP streams are idle. |
--idle-timeout |
60 s | Carrier receive, write, and outbound-queue deadline. Must exceed heartbeat interval. |
--reconnect |
off | Agent retries after carrier failure. |
--reconnect-min-delay |
1 s | First retry delay. |
--reconnect-max-delay |
30 s | Exponential-backoff ceiling. |
Retry backoff resets after a carrier attempt lasts at least 30 seconds. Interrupts also stop retry sleeps promptly. Reconnection creates a fresh session; existing TCP connections close and are not resumed.
Frames carry at most 16 KiB. Each stream direction has an eight-frame credit window. Credits return only after bytes reach the local TCP socket, so a stalled TCP destination does not block sibling dispatch or heartbeat processing. Queues and connection/connect tasks are bounded. All streams still share one ordered carrier, so loss or congestion in that transport can slow all of them.
Excess relay clients wait in the OS accept backlog; excess agent opens are rejected. Keep limits aligned across peers. Only literal loopback targets and listeners are accepted. The remote peer cannot select arbitrary destinations.
See OPERATIONS.md for supervision, health checks and recovery; PROTOCOL.md for framing; SECURITY.md for the trust model; and CONTRIBUTING.md for checks.
MIT. No hosted service or external account is required.