Use Warden

Network mediation and Egress

Two workload frontends, three boundary crossings and one authenticated Egress enforcement engine.

Egress is the only network enforcement engine in Warden. Executor does not contain an allowlist evaluator, resolver or outbound connector. It transports trusted endpoints and supervises the sandbox. The adapter offers two frontend shapes over one Egress policy/relay/receipt core:

There is no compatibility fallback: an EXTERNAL_CHANNEL spawn made directly through libmaelys-executor must supply a trusted connected AF_UNIX datagram channel. The product facade always obtains that channel from the Egress adapter.

The fd-4 shape remains native-only in 0.33.0: only the Bubblewrap and Seatbelt backends advertise NETWORK_EXTERNAL_CHANNEL. Apple Container crosses a VM boundary, so its delivered mediated path uses the proxy frontend through a guest relay and Containerization UDS/vsock transport. SCM_RIGHTS never crosses that boundary.

fd 4 frontend and SCM_RIGHTS

The workload sees fd 4 as a connected AF_UNIX SOCK_DGRAM control channel. It sends one canonical TCP host:port request with libmaelys-netclient. The adapter does not decide whether it is allowed: it calls maelys_egress_connector_session_open(). Egress authenticates the execution, checks the sealed policy, resolves and pins the destination, opens the upstream, then creates a private host-side relay stream. Only the client end of that relayed stream is returned.

SCM_RIGHTS is standard Unix ancillary-data machinery: sendmsg() attaches an open descriptor to a message and recvmsg() installs a new descriptor in the receiving process. It transfers access to the same kernel open-file description; it does not serialize bytes from the socket, cross a VM boundary, authenticate the receiver, or apply an allowlist. Those security properties come from the private channel, sandbox fd layout and Egress.

Normative/platform references:

The public client is deliberately a separate library:

CODE
#include <maelys/netclient.h>

int stream = -1;
char *error = NULL;
maelys_netclient_result_t result = maelys_netclient_connect(
    MAELYS_NETCLIENT_DEFAULT_CHANNEL_FD,
    "github.com", 443, &stream, &error);

libmaelys-netclient knows only the fd-4 wire protocol and descriptor receipt. It contains no resolver, policy or direct network connector.

Standard-client proxy frontend

This frontend lets an unmodified HTTP- or SOCKS-aware program use Egress. It does not put HTTP, SOCKS, credentials or policy into Executor's Bubblewrap relay.

This is the Bubblewrap realization of “three boundary crossings, two frontends, one Egress enforcement engine”:

  1. a standard client crosses the local loopback boundary to the relay;
  2. the relay crosses the namespace boundary through a mounted AF_UNIX socket;
  3. Egress crosses the host network boundary only after authenticating and authorizing the requested destination.

The Bubblewrap relay never speaks the fd-4 protocol and is not a second network engine.

Ownership

libmaelys_executor owns Bubblewrap setup, the statically linked maelys-guest-supervisor, readiness-before-workload, relay/workload lifecycle, bounded stop/reap and cleanup. The optional libmaelys_executor_egress_adapter is the only component that includes both Executor and Egress headers.

Egress owns the host listener, its authenticated principal, sealed destination policy, DNS/private-address checks, connect, optional TLS SNI guard, network relay, per-principal connection quotas and receipts. Private AF_UNIX bridges use an endpoint-bound principal: the mode-0600 listener path and its unchanged device/inode identity select the principal, with same-EUID peer checking as an additional condition. The Seatbelt TCP frontend and fd-4 connector retain their explicit credential contracts. libmaelys-sys supplies only generic reactors, sockets, deadlines, buffers, SIGPIPE-safe writes, half-close and idempotent descriptor closure.

Neither core library depends on the other. Linking the adapter is an explicit host decision. The adapter owns only translation and lifecycle; it never keeps a second copy of the destination allowlist.

Spawn sequence

  1. The adapter creates a short /tmp/mxnet-* mode-0700 directory. Egress creates egress.sock as mode 0600, records its device/inode identity, binds one immutable principal to that listener and reports readiness. No bearer credential is generated for this AF_UNIX route.
  2. The adapter clones the sealed destination decision to a NETWORK_MEDIATED_PROXY Executor plan and constructs an exact explicit environment. Existing proxy variables are removed; HTTP/HTTPS/ALL proxy URLs are injected and both NO_PROXY spellings are empty.
  3. Bubblewrap retains --unshare-net. After every user mount it overlays a fresh /run, mounts the Egress directory, helper and readiness directory read-only at reserved paths, and creates a sandbox-private tmpfs state directory. A user mapping of / therefore cannot mask the bridge.
  4. The environment is written to an unlinked mode-0600 host file descriptor. Bubblewrap consumes fd 4 with --file, producing a mode-0600 file only in the private state tmpfs. Proxy URLs contain no userinfo; no network secret appears in argv, environment, guest cmdline, logs or receipts.
  5. The bootstrap starts the protocol-blind relay with an empty environment and closed inherited descriptors. Relay readiness includes a successful Egress AF_UNIX preflight and a bound private loopback listener.
  6. The bootstrap reads and unlinks the tmpfs environment, starts the workload, and waits for the target exec CLOEXEC handshake. Only then does it signal readiness to Executor.
  7. On shutdown, Executor stops and reaps the Bubblewrap execution domain before the adapter stops Egress. Egress unlinks only the socket node whose identity it recorded; Executor removes only its own private directories.

Any failure before step 6 prevents workload execution. There is no ambient network or environment-only fallback.

Relay contract

maelys-guest-supervisor is one installed executable, statically linked with Maelys System on Linux. Executor read-only bind-mounts that same file into each sandbox; it does not copy the binary per spawn and the workload does not load it as a library.

The executable name is intentionally broader than the legacy relay role. Its network mode implements the sequence below; its --supervise-only mode is the Apple service guest adapter for cgroup, Bubblewrap, deadline and MWAR/2. Both roles consume the private libmaelys-guest-isolation.a preflight seam, while Egress remains the only network decision engine.

Every mediated spawn executes a private bootstrap/supervisor. That supervisor forks exactly one private relay process, waits for relay readiness, then forks and execs the workload. The relay process multiplexes all connections for that execution. It is not a global daemon and it is not forked once per proxied connection. The current adapter likewise creates a private Egress server and host thread for each execution.

The relay binds one fixed IPv4 loopback port inside the new namespace and opens one AF_UNIX stream to Egress per accepted connection. It is deliberately protocol-blind: it has no allowlist, credential, HTTP parser, SOCKS parser, DNS resolver or receipt logic.

Each direction has a separately bounded buffer. Read interest is removed when its buffer is full, providing kernel-to-user backpressure. EOF is propagated as shutdown(SHUT_WR) only after buffered bytes drain, independently in both directions. Connection slots are bounded and excess clients are closed.

The bootstrap and relay use Maelys System for the reactor and mechanical I/O primitives. The Linux helper is statically linked so the minimal sandbox view does not need a second runtime-library grant.

The relay is a process, not an fd. Its listener, accepted TCP streams, AF_UNIX streams and readiness channel are internal descriptors. The public fd4 frontend is a separate design: it gives a Maelys-aware workload a datagram control channel and does not start this relay. In proxy mode, Bubblewrap's temporary fd 4 carries the private environment file into sandbox tmpfs and is consumed before workload exec; it is unrelated to the fd4 frontend.

Seatbelt standard-client path

Seatbelt proxy support reuses Egress but not the Bubblewrap relay: macOS has no private network namespace in this design, so the standard client can reach a per-execution Egress TCP listener directly on loopback. Seatbelt must allow precisely that endpoint and continue denying ambient network and every unrelated localhost service.

Egress binds numeric 127.0.0.1 on port zero, requires proxy authentication, reports the selected port and enforces the sealed policy. The adapter reserves that same port on ::1 with a non-listening socket before credentials become workload-visible: Seatbelt's mandatory localhost:PORT spelling covers both families, so the reservation prevents an unrelated IPv6 listener from sharing the allowed port.

The implementation ownership is deliberately split:

ComponentSeatbelt proxy responsibility
Egresslistener-bound or credential authentication, destination policy, DNS/pinning, SNI guard, upstream connection, quotas and network receipts
Executor Seatbelt backendcompile and self-test the exact SBPL endpoint exception, spawn under that profile, lifecycle and fail-closed capability advertisement
Executor-Egress adaptercreate one Egress binding per execution, wait for readiness, inject authenticated proxy URLs, pass the exact endpoint to Executor, then stop and clean up both sides
libmaelys-wardenchoose this composition for frontend=proxy on a supported macOS host

The host-loopback mediated-endpoint request is distinct from the Bubblewrap-specific AF_UNIX bridge request. The Seatbelt backend advertises NETWORK_MEDIATED_PROXY only after its platform probe accepts the surgical SBPL form. Native behavioral tests additionally prove that Egress works while direct Internet, a neighbouring loopback port and the reserved IPv6 route do not provide bypasses. No policy decision or proxy parsing belongs in the Seatbelt backend.

Apple Container standard-client path

The Apple backends keep Egress on the trusted macOS host and start a pinned Linux/arm64 relay inside the route-less guest. Containerization maps the host AF_UNIX listener through its UDS/vsock transport:

The backend pins and mounts the helper and private socket after user mappings, waits for MWGB-READY/1, and admits the workload only afterwards. For apple-container-bubblewrap, the relay remains outside the inner Bubblewrap domain while the workload can reach only its loopback listener. Neither Apple candidate advertises NETWORK_EXTERNAL_CHANNEL; a future guest-aware fd4 path would need guest-local descriptor passing plus a byte relay over vsock.

Krun standard-client path

The Krun candidates use the same Egress enforcement engine with a different VM transport. Warden maps the execution-private host AF_UNIX listener to fixed AF_VSOCK port 4520. The sealed maelys-init starts the sealed relay outside the optional inner Bubblewrap domain, waits for MWGB-READY/1, and only then admits the workload. TSI and virtio-net stay disabled.

Relay bootstrap failures return MWGB-ERROR/1 <state> to PID 1 and are translated into a bounded MI/6 ERROR egress-relay-<state> response. The state names only the failed startup stage; no endpoint path, credential, allowlist or destination crosses this diagnostic channel.

Port 4519 remains the separate MI/6 lifecycle channel. The workload stdio streams use 4521, 4522 and 4523. None of these endpoints is multiplexed, and the driver payload contains neither destination allowlist nor Egress credential. The endpoint-bound principal is selected on the host before launch. The adapter pins the AF_UNIX node's identity; the backend and driver revalidate it before VM creation.

Since 0.25.0 the trusted guest init also installs a workload-only seccomp filter that denies socket(AF_VSOCK) and every io_uring entry point before exec, so a workload cannot dial port 4520 itself or bypass the filter through io_uring. The sibling relay is started outside that filter and keeps the AF_VSOCK access it needs.

Security limits

  • NETWORK_MEDIATED_PROXY is implemented by Bubblewrap through its private relay, by Seatbelt through its host-loopback filter, by Apple Container through UDS/vsock, and by Krun through AF_VSOCK 4520.
  • The bridge supports programs that honor HTTP(S) or SOCKS proxy variables. A program that ignores them cannot reach external IP networks; it does not get transparent interception.
  • Private AF_UNIX routes carry no credential. Authorization is bound to the execution-private endpoint identity and immutable principal, while SAME_EUID remains an additive host check rather than the authorization source. A credential from one execution therefore cannot leak into or be replayed by another execution because none exists on this route.
  • Bubblewrap's proxy mode permits AF_INET/AF_INET6 only in the workload; those sockets see the otherwise empty network namespace and its private loopback. A workload-only seccomp filter denies socket(AF_UNIX) and every other socket family, so a Unix socket visible through a user bind cannot become a second host path. The relay starts outside that filter and alone dials the reserved Egress socket. socketpair(AF_UNIX) remains available for process-local IPC, and user mounts cannot override backend-owned paths.
  • This does not strengthen Executor's separate PROCESS_TREE_LIFECYCLE guarantee. A hostile descendant that deliberately escapes the tracked process domain remains outside the capability claimed by current local backends.

Verification

make egress-adapter-check compiles the optional seam whenever a local Egress checkout is present. make docker-egress-bridge runs complementary privileged Ubuntu coverage using real Bubblewrap and unmodified curl for HTTP and SOCKS. Release-grade confinement still requires native Linux amd64 and arm64 runners; privileged Docker alone is not a kernel-security proof.

The bridge tests cover bounded buffers, bidirectional half-close, client and Egress disappearance, connection limits, relay/Egress failure before readiness, root-mount masking, direct external and unrelated-loopback denial, denial of workload-created Unix sockets, continued private-loopback socket operation, and absence of internal descriptors or proxy credentials in workload-visible argv.