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:
- POSIX defines ancillary data,
cmsghdr,CMSG_*andSCM_RIGHTSin<sys/socket.h>. - Linux documents descriptor passing and ownership in
unix(7)and thecmsg(3)helpers. - Darwin exposes the same
sendmsg/recvmsgancillary-data interface in the Applerecvmsg(2)manual.
The public client is deliberately a separate library:
#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”:
- a standard client crosses the local loopback boundary to the relay;
- the relay crosses the namespace boundary through a mounted AF_UNIX socket;
- 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
- The adapter creates a short
/tmp/mxnet-*mode-0700 directory. Egress createsegress.sockas 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. - The adapter clones the sealed destination decision to a
NETWORK_MEDIATED_PROXYExecutor plan and constructs an exact explicit environment. Existing proxy variables are removed; HTTP/HTTPS/ALL proxy URLs are injected and bothNO_PROXYspellings are empty. - 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. - 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. - 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.
- 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.
- 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:
| Component | Seatbelt proxy responsibility |
|---|---|
| Egress | listener-bound or credential authentication, destination policy, DNS/pinning, SNI guard, upstream connection, quotas and network receipts |
| Executor Seatbelt backend | compile and self-test the exact SBPL endpoint exception, spawn under that profile, lifecycle and fail-closed capability advertisement |
| Executor-Egress adapter | create 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-warden | choose 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_PROXYis 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_EUIDremains 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_INET6only in the workload; those sockets see the otherwise empty network namespace and its private loopback. A workload-only seccomp filter deniessocket(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_LIFECYCLEguarantee. 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.