burrowed
Team-only, self-healing SSH tunnels over Cloudflare. The package is public but
carries zero secrets and does nothing without a currently-valid, server-issued
key. Boxes need no gh/npm login: the tool is npx-run and SSH keys are pulled
from https://github.com/<user>.keys.
Linux targets. Every command runs silently and fully detached as a background
daemon, self-heals through tunnel death and reboots, and keeps no local logs —
crashes, errors, and status are forwarded to the registry, never written to the box.
Install
No install needed on a box:
npx burrowed <command> ...
As a dependency for the SDK:
npm i burrowed
Keys
Two credentials, split by entropy:
- Join key — what you type: three words plus two digits (
swift-tiger-comet-42).
Server-minted, short TTL, single-use by default, rate-limited. Carries a role:
node (expose + snapshot), reader (list/view/pull), or admin.
- Session token — 256-bit, returned by enroll, held only in daemon memory and
slide-refreshed. Never on a command line or disk.
--root <ROOT_KEY> is the admin override. Root enrolls as an admin session and can
do anything — list, ssh, kill, and snapshot any box. Admins hold it; it is never
required on a box. Admin commands also read BURROW_ROOT_KEY from the environment.
Commands
burrowed on (<key> | --root <K>) [--github <user>]... [--pubkey <line>]... [--restart]
burrowed getboximage (<key> | --root <K>) [--manifest | --content] [--path <dir>]...
burrowed ssh <id|name|latest> (<key> | --root <K>) [--githubsshkey <file>] [--user <u>]
burrowed codepull <id|name|latest> (<key> | --root <K>) [--out <dir>] [--path <box-dir>]
burrowed codepush (<key> | --root <K>) [--src <dir>] [--id <box>] [--dest <path>] [--mirror]
burrowed list (<key> | --root <K> | --token <T>)
burrowed kill <id|all> (<key> | --root <K> | --token <T>)
on — expose this box
Fire-and-forget. Backgrounds a daemon that opens a Cloudflare tunnel to the box's
SSH and registers it. The tunnel URL is never printed or persisted on the box —
discover boxes centrally with list.
npx burrowed on swift-tiger-comet-42 --github alice --github bob
getboximage — snapshot the box into the registry
Default --manifest: a file tree plus small text snippets, with secrets
(.ssh/.env/keys/cloud creds) and heavy dirs excluded, reading only what the
running user can already access. --content (alias --full) also streams full file
bytes to R2. Browse with images/image, restore with pull.
npx burrowed getboximage swift-tiger-comet-42
npx burrowed getboximage swift-tiger-comet-42 --content
codepull / codepush — edit box code without SSH (CLI only)
Round-trip one box directory from your machine with no SSH and no gh/npm login
on the box. Both ride the heartbeat command channel, so they work even while the
SSH tunnel is flapping — the box only needs to be heartbeating.
codepull parks a fresh full-snapshot order, waits for the box to take it, then
downloads the result flattened into --out (default ./<id>) and writes a
.burrowed-origin.json stamp naming the box and its source root. node_modules,
.git, and secrets (.ssh/.env/keys/creds) are excluded — source and images only.
npx burrowed codepull latest --root <ROOT_KEY> --out ./box
npx burrowed codepull my-box-a1b2 <node-key> --path /srv/app
codepush reads that stamp for the box id, destination, and registry (override with
--id/--dest/--src), uploads your local edits, and parks an apply the box runs
on its next heartbeat. Overlay by default (create/overwrite, never delete); --mirror
also deletes box-side files you removed locally, sparing node_modules/.git/secrets.
cd ./box && npx burrowed codepush --root <ROOT_KEY>
npx burrowed codepush <node-key> --src ./box --dest /srv/app --mirror
Writing into a box is least-privilege: the owning node key (the one that ran on)
or --root/an admin may push; reader keys never can. codepull's box must be live.
ssh — drop into a box (CLI only)
Resolve a box by instance id, machine name, an unambiguous id prefix, or latest,
then open a shell over the tunnel. CLI-only by design; this is not in the SDK.
npx burrowed ssh latest --root <ROOT_KEY> --githubsshkey ~/.ssh/id_ed25519
npx burrowed ssh my-box-a1b2 swift-tiger-comet-42
list — every active team tunnel
npx burrowed list swift-tiger-comet-42
kill — remote teardown
Marks a box (by id) or every box in scope (all) for self-destruct. The registry
drops the listing immediately; the box obliterates itself — daemon, service, tunnel,
and local state — on its next heartbeat. A bound key kills only what it owns; root
or an admin token kills anything.
npx burrowed kill my-box-a1b2 --root <ROOT_KEY>
npx burrowed kill all swift-tiger-comet-42
Persistence (reboot survival)
on self-heals the tunnel (edge death, 530 zombies, link loss) and survives the
shell that launched it, but the daemon it spawns has no supervisor — a host reboot
or a hard process kill ends it. For a box that must come back on its own, install a
supervisor:
npx burrowed install-service swift-tiger-comet-42 --github alice
npx burrowed uninstall-service
On Linux this writes a systemd --user unit (Restart=always, started on boot via
linger) that runs the same worker; on macOS, a launchd agent. It restarts on crash
and starts at boot. Trade-off: unlike on, this writes the key into a 0600 unit
file on the box, so prefer a longer-lived, multi-use key for it. kill and remote
self-destruct remove the unit as part of teardown.
SDK
A key-bound client for apps running on a box. Set the key once; every call is gated
by it. ssh is intentionally absent — it is CLI-only. Each command is four lines or
fewer:
import { burrow } from "burrowed";
const b = burrow({ key: process.env.BURROW_KEY });
const handle = await b.on({ github: ["alice"] });
const snap = await b.snapshot();
const full = await b.snapshot({ mode: "full" });
const { tunnels } = await b.list();
await b.kill("all");
await b.kill(handle.id);
The key is the only secret; nothing here works without a live one.
Env: BURROW_KEY, BURROW_ROOT_KEY, BURROW_REGISTRY, BURROW_SSH_GITHUB (csv),
BURROW_SSH_PUBKEY (csv).