Security and evidence
Security model
Trust boundaries, fail-closed guarantees, deliberate limits, descriptor ownership and supply-chain claims.Trust boundary
Maelys Warden enforces a decision; it does not make one. Its low-level Executor
engine receives the executable path, argv, environment, working directory and
sealed SandboxPlan. Those inputs must come from a trusted host configuration
or an external policy decision point, never directly from an untrusted MCP
request or agent-controlled project file.
Backend selection is a capability intersection. Every capability required by a sealed plan must be present. An unavailable sandbox backend never falls back to POSIX for a confined plan.
POSIX
POSIX provides process creation, fd layout and bounded process-group shutdown.
It advertises only EXECUTION. A descendant can call setsid() and escape the
group, so POSIX is not an adversarial sandbox.
Bubblewrap on Linux
Bubblewrap uses user, PID, IPC and UTS namespaces, drops capabilities,
installs fresh /proc and /dev, and uses a network namespace for
NETWORK_IP_NONE. For NETWORK_NONE and EXTERNAL_CHANNEL, it additionally
installs a classic BPF seccomp filter that validates the native audit
architecture and denies socket, connect, ptrace, process-vm and io_uring
entry points. x86-64 x32 syscalls are killed rather than falling through the
native-number comparisons. Its behavioral probe runs the actual required
flags with an empty environment, closed inherited descriptors and a deadline.
Bind sources are canonicalized and restricted to regular files/directories at
prepare time. Device and inode identities are checked again immediately before
spawn. Nested user mount destinations are rejected; /proc and /dev are
reserved and installed last. Host-controlled source ancestors remain a trust
assumption because Ubuntu 24.04's Bubblewrap does not provide every fd-based
mount primitive needed to eliminate the final pathname race.
The filesystem begins empty. READ and WRITE rules become canonical pinned bind
mounts, then the synthetic ancestor tree is frozen read-only. Explicit DENY
rules are installed last as mode-000 read-only mount overlays, so a broader
WRITE grant cannot rename, unlink or expose the denied mountpoint. Safe host
compatibility symlinks such as /lib -> usr/lib are reconstructed only when
the corresponding host link is relative and contains no dot components; they
grant no path that was not already mounted by the plan.
Bubblewrap claims strong NETWORK_NONE, explicit DENY, syscall filtering and
Unix-socket control under this construction. It does not claim complete
hostile-descendant supervision. A caller choosing the weaker
NETWORK_IP_NONE explicitly may still expose a pathname-addressed Unix socket
through an authorized bind mount.
Seatbelt on Apple Silicon
Seatbelt is supported on native arm64 macOS 15 and newer. The probe requires a
root-owned, non-writable /usr/bin/sandbox-exec and executes a behavioral
profile before advertising capabilities. The tool is deprecated by Apple, so
it is isolated behind the same backend ABI as future Darwin mechanisms.
The profile starts from deny-default, allows the minimum process and platform
runtime operations needed by native tools, adds plan READ/WRITE rules as data
parameters. Direct network operations are absent for NETWORK_NONE. View
remapping, tmpfs and explicit DENY rules are rejected. DENY remains in the plan
vocabulary, but no local backend claims it until rename-safe enforcement is
available.
Seatbelt policy inheritance does not make POSIX process-group lifecycle
complete: a hostile descendant can create another session. Consequently the
backend advertises PROCESS_CONFINEMENT, not PROCESS_TREE_LIFECYCLE.
Path rules are pathname enforcement. Existing symlink rule roots are rejected. Global file metadata access is part of the Darwin runtime baseline, so the backend protects contents and mutation, not path-existence secrecy. Rename and absent-path hardening beyond the explicitly claimed capabilities remains for a later release.
Apple Container 1.3.0
The two optional Apple candidates accept only immutable OCI TargetSpecs. The
image reference is paired with a mandatory sha256: digest, and the Swift
driver verifies the resolved image digest before reporting READY. Warden
pins the driver's canonical path, owner, mode, device, inode and SHA-256 across
probe, prepare and spawn. A missing driver or runtime is unsupported, never a
reason to select a native backend for the OCI target.
apple-container uses the VM boundary, a read-only OCI root, pinned VirtioFS
mount sources and tmpfs. It does not advertise portable filesystem access
rules or ABI-10 resource guarantees: a root workload can modify the service
cgroup. apple-container-bubblewrap additionally lowers recursive READ/WRITE
rules inside the Linux guest. The Apple service applies the exact VM vCPU and
memory dimensions. Before the workload starts, Warden's trusted bootstrap moves
Apple's PID 1 and its own helpers into a supervisor cgroup, creates a separate
workload cgroup with exact memory, swap, CPU and task limits, and places the
Bubblewrap subtree inside it. Bubblewrap exposes cgroupfs read-only, so the
workload cannot widen those limits. The bootstrap reports measured peaks,
events, wall-deadline outcome and verified cgroup cleanup through MWAR/2;
missing or malformed evidence makes the run fail closed.
The composite driver explicitly clears Apple Container's
masked and read-only proc paths, then Bubblewrap installs a fresh procfs inside
the nested PID namespace while retaining its user namespace. The workload has
empty effective and bounding capability sets and NoNewPrivs; failure to
mount procfs aborts admission rather than exposing the inherited guest view.
Explicit DENY, create protection and an inner syscall filter remain separate,
unadvertised semantics. Missing or unusable Bubblewrap causes failure of the
composite rather than fallback to the plain candidate.
For NETWORK_NONE, Apple Container receives no ambient network attachment.
For mediated networking, a pinned guest relay reaches the execution-private
host Egress listener through Containerization's UDS/vsock bridge. The relay is
protocol-blind and the workload receives no host descriptor. Native fd-4
SCM_RIGHTS mediation is not advertised because descriptor passing cannot
cross the VM's kernel boundary.
Warden drives lifecycle with its framed control protocol, deadlines and a
final host-side kill of an unresponsive Swift driver. The candidates do not
advertise PROCESS_TREE_LIFECYCLE: killing the driver alone does not prove
that the Apple service deleted every VM resource. See
apple-container.md and the
capability matrix.
The optional Krun package may be discovered as two ABI 10 candidates. Both
admit only a digest-pinned OCI linux/arm64 target whose ext4 root, artifact
seal, driver, firmware libraries and guest bundle remain identical between
preparation and spawn. krun supports view mounts, tmpfs and network NONE.
krun-bubblewrap additionally lowers recursive READ/WRITE rules through a
static helper whose digest is bound by the guest manifest, artifact seal and
candidate evidence. It mounts a fresh procfs inside its private PID namespace;
failure to create that procfs aborts admission before READY. DENY, literal
rules and create protection remain fail-closed. For mediated proxy networking,
the Warden-owned relay listens only on the guest loopback and dials AF_VSOCK
port 4520. libkrun TSI and virtio-net remain disabled; the mapped host endpoint
is the identity-pinned per-execution Egress AF_UNIX listener. Before exec the
guest init installs a workload seccomp filter that denies AF_VSOCK socket
creation and every io_uring entry point, so only the sibling relay reaches
that mapping. The host VMM driver is itself wrapped in a behaviorally proven
deny-default Seatbelt profile whose identity and pinned dynamic-library closure
enter candidate evidence.
Krun and direct Containerization may additionally admit the canonical
ROOT_EPHEMERAL_WRITE decision. The ext4 lower remains open read-only and is
measured before admission. A per-execution tmpfs of the exact RunSpec ceiling
holds OverlayFS upper/work data; workload/Bubblewrap starts only after a
write/delete probe. PID 1 reaps the execution domain before unmounting and MI/6
reports the applied size and cleanup result. A forced VMM teardown destroys
the guest memory even when synchronous guest cleanup cannot complete.
The direct apple-containerization candidates own their VZ VM lifecycle and
boots Warden's pinned kernel and PID 1. Its host driver is wrapped in a
deny-default Seatbelt profile compiled from the pinned kernel, init, root and
view paths. A behavioral probe proves denial of an out-of-profile file and IP
network before registration. The per-execution writable directory is 0700 and
cleaned by Warden. Its code-signing entitlement merely permits
Virtualization.framework use; it is not confused with the Seatbelt guarantee.
For linux/amd64, selection additionally requires an installed, stable
Rosetta identity; the guest mounts the Rosetta share read-only and registers
only the x86_64 ELF handler.
apple-containerization-bubblewrap adds the same guest-side recursive
READ/WRITE lowering used by krun-bubblewrap. The Swift driver transports the
resolved rules but cannot compile or widen them. Warden's PID 1 invokes the
sealed /maelys-bwrap through libmaelys-guest-isolation.a, requires a
bounded preflight before workload admission and drops all capabilities. The
simple candidate carries an explicit false selector and is rejected whenever
the plan requires FS_ACCESS_RULES.
Descriptors and environment
Executor-created descriptors are close-on-exec until deliberately mapped.
Linux uses posix_spawn_file_actions_addclosefrom_np; macOS uses
POSIX_SPAWN_CLOEXEC_DEFAULT. Isolated duplex maps protocol bytes to child fd 3,
stdin to /dev/null, and stdout to stderr. The kernel fd layout protects MCP
JSON from noisy stdout in every provider language.
Explicit and empty environment modes prevent ambient credential inheritance. Inherit mode is an explicit host choice. Probe programs always receive an empty environment and no inherited descriptor above stderr.
Executor owns the wait status of processes it starts. An embedder must not reap
these children from a global SIGCHLD handler or enable SA_NOCLDWAIT.
Mediated network
An EXTERNAL_CHANNEL plan requires a non-empty canonical TCP hostname/port
allowlist and confinement. Executor does not implement that allowlist,
resolve DNS or call outbound connect(). ABI 10 requires the trusted host to
install a connected blocking AF_UNIX datagram channel in the request; the
backend denies ambient networking and maps only its child endpoint to fd 4. A
missing channel, a channel on another socket family/type, or a channel attached
to a non-external plan is rejected before backend prepare.
The Warden–Egress adapter creates the pair and translates each validated fd-4
wire request to Egress's authenticated native connector. Egress is the sole engine
for the sealed destination policy, DNS/pinning, private-address decision,
connect deadline, optional TLS SNI guard, byte relay, quotas and network
receipts. It returns a private relay client stream, never the upstream socket.
The broker passes that stream to the workload with SCM_RIGHTS; it keeps no
allowlist and performs no direct network operation. Calls sharing one fd 4
must be serialized.
SCM_RIGHTS transfers a reference to a same-kernel open file description. It
does not authenticate, authorize or cross a VM boundary. Channel privacy,
credential binding, the sandbox descriptor layout and Egress policy supply
those guarantees. See docs/egress-bridge.md for normative Unix references and
the complete sequence.
The same Executor-Egress adapter serves unmodified HTTP/SOCKS
clients. Under Bubblewrap it creates one endpoint-authenticated AF_UNIX Egress listener
per execution and selects NETWORK_MEDIATED_PROXY. Bubblewrap retains a fresh
network namespace. The Executor-owned protocol-blind relay may use loopback
and AF_UNIX, while a workload-only seccomp layer permits only AF_INET/AF_INET6
socket creation in that route-less namespace. Thus a Unix socket exposed by a
user bind mount cannot bypass Egress. The backend mounts the private Egress socket
path after all user mounts, starts the relay before the workload, and injects
proxy URLs through a secret-free launcher argv path.
Under Seatbelt the adapter instead starts an authenticated Egress TCP listener
on a random 127.0.0.1 port. The backend admits outbound traffic only to the
corresponding localhost:PORT SBPL endpoint. Because that Seatbelt token also
covers ::1, the adapter binds the same IPv6 port without listening before it
exposes credentials; a second host service cannot occupy the admitted port.
No relay runs on macOS. Native tests prove Egress reachability together with
denial of a neighbouring loopback port and direct external traffic.
The relay has bounded bidirectional buffers and connection slots but no
credential, allowlist, DNS or protocol parser. Egress alone assigns the
endpoint-bound principal while parsing HTTP CONNECT/SOCKS, applies the sealed
destination decision, resolves/connects, guards requested TLS SNI, enforces the
per-principal connection quota and emits byte/closure receipts.
A private AF_UNIX bridge carries no bearer secret: the
mode-0600 listener path and its unchanged device/inode identity select one
immutable principal, and same-EUID peer checking stays an additive host
condition rather than the authorization source. The TCP Seatbelt and fd-4
frontends keep their explicit credential contracts. See
egress-bridge.md.
Termination and receipts
Local shutdown performs a bounded graceful rung followed by a bounded forced rung. If containment cannot be observed, it returns an error and retains the handle; local-only release can deliberately abandon tracking without blocking.
Receipts are immutable snapshots, not cryptographic attestations. The plan digest is SHA-256 over the canonical plan representation. Backend name/version and capabilities describe the enforcement path selected by this process; they do not prove a remote host or kernel state.
Not claimed in 0.40.0
- policy correctness;
- TLS termination or content inspection in Executor core;
- cgroup quotas for native workloads or a comprehensive syscall allowlist;
- kernel-vulnerability protection;
- complete supervision of descendants that escape a process group;
- remote execution attestation;
- arbitrary or caller-supplied guest syscall policies;
- persistence of writes made through the ephemeral root overlay;
- exact usable payload bytes equal to the tmpfs ceiling (metadata consumes part of the bounded filesystem);
- native fd-4 descriptor mediation across a VM boundary;
- safety after a trusted caller authorizes sensitive mounts or inherited credentials.
Release supply chain
Release integrity is a separate boundary from process confinement. A signed
tag authorizes publication, SHA-256 checksums detect changed downloads, and
GitHub/Sigstore attestations bind artifacts to this repository's release
workflow. These mechanisms establish origin and provenance; they do not prove
that a backend, kernel or policy is vulnerability-free. Verification commands
and the build/publish permission split are documented in
release-integrity.md.