Skip to content

Inbound Ports

By default nothing outside can reach a process inside. expose opens a host-loopback port and relays it in.

net {
expose ["3000", "127.0.0.1:8080"]
}

Both forms mean the same thing; the address half is accepted only for explicitness and must be loopback (127.0.0.1 or [::1]). 0.0.0.0 is an error — expose never opens a port to your network.

expose ["*"]

means different things on each platform, deliberately:

macOSa bind permission. “Any port” costs nothing, so * grants everything.
Linuxthe sandbox has its own loopback, so each port needs a real relay. There is no sane reading of “listen on all 65535”, so * enumerates nothing.

So * grants everything macOS can grant and nothing Linux has to enumerate. A port you need reachable on Linux must be named. policy explain reports the difference.

fragment "dev-server" {
selects_on = { commands = ["npm", "vite"] }
net {
expose ["5173"] # named, so it works on both platforms
}
}
Terminal window
wardcell run -- npm run dev
# then open http://localhost:5173 in your browser

expose is only the inbound direction — something outside reaching a listener inside the sandbox. Two related needs are documented elsewhere:

  • A sandbox process calling another process in the same sandbox over localhost needs nothing for TCP — it works on any port, no expose and no grant. UDP on loopback is still gated, so a UDP helper needs an allow_udp ["127.0.0.1:PORT"] grant. See macOS and Linux.
  • A sandbox calling a service on your machine’s loopback is an outbound grant. The portable way is the reserved host.internal — allow_tcp ["host.internal:5432"] — which reaches the host on both platforms. A literal 127.0.0.1 grant also works on macOS, but on Linux names only the sandbox’s own loopback. See the platform pages for the distinction.