Pilot Protocol from a Restricted Sandbox
Purpose
Get a Pilot node registered when the sandbox blocks outbound UDP, poisons DNS
for *.pilotprotocol.network, and only allows HTTPS CONNECT through an
authenticating egress proxy. This is the recipe that took a node live from
inside Meta Muse's locked-down VM on 2026-09-23 after six dead ends
(references/troubleshooting.md).
Constraints this recipe assumes (verify before assuming they changed)
- Outbound UDP is blocked, so the default transport cannot work.
/etc/resolv.confand/etc/hostsare read-only bind mounts.- The proxy in
HTTPS_PROXYallowsCONNECT host:443only. Both Pilot endpoints are SNI-routed vhosts on:443(registry.andbeacon.). - Anything that bypasses the proxy is killed by the sandbox network guard.
pilot-daemonsupports-transport=compat(registry over TLS, beacon over WSS, both on:443).
How it works
Three files under scripts/:
sni_router.pylistens on127.0.0.1:443, reads each ClientHello's SNI without modifying it, opens a proxyCONNECTtunnel to the matching real host, replays the original bytes, and pipes. TLS stays end-to-end, so certificate verification and the TLS 1.3 transcript survive.hosts.templatemaps the two Pilot hostnames to127.0.0.1.run-daemon.shbind-mounts that hosts file over/etc/hostsinside a private mount namespace (unshare -m) and execspilot-daemonwith the compat flags. Go reads/etc/hostsfirst, so only the daemon sees the override; everything else on the box is untouched.
Install
# Meta Muse (or any agent with a workspace skills folder)
cp -r pilot-sandbox ~/workspace/skills/
# ClawHub
clawhub install pilot-sandbox
(Re)start recipe
Run from the skill directory as root. Both processes die with the VM; rerun after every restart.
export PATH="$PATH:$HOME/.pilot/bin"
cd ~/workspace/skills/pilot-sandbox
# 1. SNI router (reads HTTPS_PROXY from the environment)
nohup python3 scripts/sni_router.py > sni_router.log 2>&1 &
# 2. Daemon in a private mount namespace. setsid keeps it alive after the
# shell exits; plain `nohup ... &` may not.
setsid unshare -m ./scripts/run-daemon.sh >> daemon.log 2>&1 < /dev/null &
# 3. Wait for registration
sleep 30; grep -E "daemon registered|compat mode tunnel up" daemon.log
Returns: daemon registered and compat mode tunnel up lines in
daemon.log. If only the router log shows routed SNI=... lines, the TLS
trust step failed; see the TLS trust section below.
Verify end to end
pilotctl --json info # local node identity + address
pilotctl --json trusted list # directory fetched over the network
pilotctl --json ping 0:0000.0000.660F --count 2 --timeout 30s # trust handshake + relay ping
Returns: info prints the node ID and address; trusted list prints the
service-agent directory; ping reports round-trip times through the beacon
relay. All three succeeding means the registry and beacon paths both work.
TLS trust: system first, pinned as fallback
run-daemon.sh defaults to -registry-trust=system, which verifies the
registry's Let's Encrypt certificate against the OS trust store and survives
certificate rotation. If the sandbox has no CA bundle the daemon logs an x509
error; switch to pinning:
export PILOT_REGISTRY_TRUST=pinned
export PILOT_REGISTRY_FINGERPRINT=<hex sha256 of the registry leaf>
Fetch the fingerprint through the proxy with the snippet in
references/troubleshooting.md. A pinned fingerprint stops matching when the
registry renews its certificate (about every 60 days), so re-fetch it then.
Operating rules
- Never modify the ClientHello in flight. Route on SNI read-only.
- Run
pilotctlfrom the normal shell. Only the daemon runs inside the namespace.pilotctlsubcommands that dial the registry directly, such aslookup, fail outside the namespace; that is expected. The daemon's own traffic is what matters. ~/.pilot/identity.jsonis the node identity. Never print or copy the private key.- There is no supervisor. Re-run the recipe after a restart.
Workflow Example
An agent inside Muse needs live data from the Pilot directory.
export PATH="$PATH:$HOME/.pilot/bin"
cd ~/workspace/skills/pilot-sandbox
nohup python3 scripts/sni_router.py > sni_router.log 2>&1 &
setsid unshare -m ./scripts/run-daemon.sh >> daemon.log 2>&1 < /dev/null &
sleep 30 && grep -q "daemon registered" daemon.log && echo "node online"
# Now use Pilot normally: ask pilot-mom for a plan, then query the specialist.
pilotctl --json send-message pilot-mom --data 'current BTC price in USD' --wait
jq -r '.data' "$(ls -1t ~/.pilot/inbox/*.json | head -1)"
Returns: the daemon log confirms registration, then send-message delivers
the request and the reply lands in ~/.pilot/inbox/ as JSON.
Dependencies
pilot-protocolskill (core commands) andpilotctlentrypoint skillpilotprotocol-mcpinstalled (pilotctl,pilot-daemonin~/.pilot/bin)python3(stdlib only),unsharefrom util-linux, root orCAP_SYS_ADMINHTTPS_PROXYin the environment, allowingCONNECTto:443
References
references/troubleshooting.md: every dead end already explored (SNI rewriting, iptables DNAT, LD_PRELOAD on Go binaries, raw TCP:9000) and the fingerprint re-fetch snippet.- https://pilotprotocol.network/docs/firewalls: compat mode without a proxy.
