How to install, run, and pair wire inside container / egress-restricted /
AI-security-scanner sandboxes — and the one footgun (custom-port relays) plus its
fix. Everything in the Docker section below was validated in-situ on
2026-06-29 against the deploy/Dockerfile image (distroless musl static, 12.8 MB).
deploy/Dockerfile builds one ~13 MB distroless image that runs every wire role
(relay-server / daemon / mcp / client) — pick the role at CMD. See
deploy/README.md for the build + role matrix.
wire writes only under $WIRE_HOME (/data in the image), opens TCP from
non-privileged ports, and needs no Linux capabilities. So the full container
hardening set works as-is — verified: wire --version, wire whoami,
wire up <relay> (mint identity + bind + claim + start daemon), and a
container-to-container encrypted message round-trip all succeed under:
docker run --rm \
--read-only \ # root FS immutable; state goes to the volume
--tmpfs /tmp \ # distroless has no writable /tmp
--cap-drop=ALL \ # wire needs no capabilities
--security-opt=no-new-privileges \
--pids-limit=200 \
-v wire-state:/data \ # the ONLY writable path wire needs
wire:local wire up http://relay:8770 --no-local--read-only holds because wire's persistent writes (config, identity key, inbox
JSONL, slot cursors, atomic tmp+rename) land under /data — and the one path that
writes elsewhere, wire identity create --anonymous (which stages under the system
temp dir, i.e. /tmp), is covered by the --tmpfs /tmp in the recipe. Don't drop
--tmpfs /tmp on the assumption "everything goes to /data". --cap-drop=ALL holds
because the relay binds 8770 (> 1024) and clients open only outbound sockets.
deploy/compose.yml brings up a relay + a daemon. To prove a full pair +
message between two separate container identities, see the
"custom-port relays" note below — on a non-wireup relay you must pair with the
invite flow, not wire dial <handle>.
A wire federation handle is nick@domain and assumes a public, port-less
HTTPS domain (e.g. alice@wireup.net, implicitly :443).
Loopback relays are dialable by handle (E4). is_valid_domain accepts a
:port suffix when the host is a loopback literal (127.0.0.1 / localhost),
and the client speaks http:// to it (local relays don't terminate TLS). So a
same-box / loopback sandbox relay works directly:
wire dial knit-ash@127.0.0.1:8771 "hello" # resolves over http://127.0.0.1:8771Non-loopback hosts stay port-less (evil.com:8443 is rejected) — a public relay
on a custom port should sit behind a 443 TLS edge (Cloudflare Tunnel / Caddy).
A custom-port relay reached by an internal DNS name (e.g. the Docker
service name relay.wire.local:8770) still can't be a handle: http-vs-https
isn't inferable from the name. Pair via the invite flow, which carries the
full relay URL (scheme + port) in the token — validated container-to-container
on a :8770 relay:
# On peer A (already `wire up`-ed against the local relay):
A_INVITE=$(wire invite --relay http://relay.wire.local:8770 --json | jq -r .invite_url)
# On peer B:
wire accept-invite "$A_INVITE" # pins A, exchanges signed cards
wire send <A-persona> "hello" # now A is a pinned peer; bare nick resolvesSame-box sibling sessions (one $WIRE_HOME, distinct WIRE_SESSION_ID) can use
bare-nick wire dial directly — they resolve as local sisters, not federation.
See scripts/hello-world-validate.sh for that path.
Security note: loopback handle dialing widens the prompt-injection SSRF surface to loopback ports (T14 in
docs/THREAT_MODEL.md); the bilateralwire_acceptgate and the poisoned-card fingerprint hard-refuse (now on the MCP path too) still apply.
landing/openshell-policy.sh (also served at https://wireup.net/openshell-policy.sh)
runs on the OpenShell host and grants a named sandbox the minimum egress wire
needs: GitHub release assets (install) + wireup.net (runtime), nothing else.
curl -fsSL https://wireup.net/openshell-policy.sh | bash -s <sandbox-name>
# then, inside the sandbox:
curl -fsSL https://wireup.net/install.sh | sh
wire init <handle> --relay https://wireup.netThe runtime allow-list is wireup.net-only (single relay). Pairing against a
handle on a different relay domain needs that domain added to the policy's
--add-endpoint / --add-allow set.
Drift guard: tests/openshell_policy_coverage.rs asserts the policy's
allow-list covers every relay route src/relay_client.rs calls — so a new
client call can't silently get refused inside the sandbox. (It caught a missing
DELETE /v1/handle/claim/:nick allow that broke handle-release in-sandbox.)
If you add a relay call to the client, add it to REQUIRED_ROUTES in that test
and to the wire_runtime rule in the policy.
AI-posture scanners inventory the AI agents + MCP servers on a device and score
them. wire shows up as both a CLI on PATH and a configured MCP server
(the wire entry in an agent's MCP config). Two things to know:
- wire is not (yet) in such registries' curated MCP catalogs, so it is flagged as an unknown / unvetted MCP until added. Its risk profile is dominated by the traits that define it: it egresses to a relay (not a local-only MCP), runs a persistent daemon, and passes inbound peer messages into the agent's context.
- The clean dimension: wire's MCP tools are messaging only — no shell execution, package management, or destructive local commands.
To scan wire in isolation (without exposing a whole host's MCP inventory to the scanner vendor), run the scanner inside a throwaway container whose only MCP is wire, rather than installing the host agent on a daily-driver machine.