Threat model
What procage defends against (adaptive same-user code in the child), what it trusts, where an OS sandbox stops, and the status of its escape classes. A summary of the canonical THREAT_MODEL.md.
This page summarises procage’s threat model. The canonical text, with every escape class, its evidence and its open variants, is THREAT_MODEL.md, also published as the procage::threat_model module. Where they differ, the file is right.
Adversary#
Arbitrary, adaptive code inside the caged child, with the same user id as the caller: a language model following injected instructions, or a malicious build script. It can fork and exec, and use files and network as the policy grants.
Assets#
- Secrets: SSH, cloud and registry credentials, tokens in the environment, the Keychain, browser profiles.
- Files outside the paths the policy grants.
- The host session: the terminal, the parent shell, the graphical session.
- Persistence locations: launch agents, cron, login items, shell startup files, version-control hooks.
- Other runs, and the calling program’s own binary and state.
Trusted#
The parent process and the policy it passes; the kernel; the OS sandbox facilities (libsandbox and libSystem on macOS; Landlock, seccomp and namespaces in the Linux kernel).
In scope and out of scope#
In scope: escapes caused by misconfiguration, the known escape classes, leaks through inherited state (file descriptors, environment, the controlling terminal, Mach ports), persistence, and descendants that outlive the run.
Out of scope: bugs in the kernel, XPC services, TCC or Linux security modules; side channels; denial of service beyond the resource limits; and trust handoff beyond the shipped lists: when a program outside the sandbox later runs or trusts something the sandbox wrote, procage covers the known locations with its hard-deny classes, and every other location is the calling harness’s job (R-07).
How strong an OS sandbox is#
An OS sandbox reduces the risk from adaptive same-user code; it does not eliminate it. The kernel and the OS services stay reachable, and a bug in them is an escape. In exchange it starts far faster than a virtual machine and runs the host’s own tools.
When a kernel or service escape is unacceptable, use a microVM (a hypervisor boundary), optionally with procage inside it for finer-grained policy.
Undocumented interfaces#
- macOS: Seatbelt profiles are compiled and applied through libsandbox functions Apple does not document or support, resolved at run time; finding a run’s processes uses an undocumented query. Any macOS release may remove them. Missing or changed behaviour is an
Unsupportederror, never a silent fallback. - Linux: procage measures the kernel (Landlock ABI, seccomp, unprivileged user namespaces) and assigns a tier. A policy the tier cannot enforce is an error naming the missing capability, never applied best-effort.
- Nesting: inside another Seatbelt sandbox a second profile fails or has no effect; procage refuses up front.
Escape classes#
Each way a caged process could escape or leak is an escape class with a stable id (R-02 to R-36, with gaps). Each has a disposition (mitigate, detect or harness) and a status. A class is mitigated only with evidence on every platform it applies to (caged canaries on a real kernel, next to a control showing the operation succeeds where allowed) and only when no variant is known to be open.
- Mitigated: non-canonical path rules (R-02), pattern characters in paths (R-03), ancestor rename (R-04), writes through read-only descriptors (R-05), work handed to unsandboxed services (R-06), pre-existing hard links (R-08, refused before each spawn), local daemons (R-09), a shared temporary directory (R-10), the unsandboxed exec modifier (R-11), firmlink spelling (R-12), inherited file descriptors (R-21), terminal input injection (R-22), outliving the run (R-23), secrets in the environment (R-24), process introspection (R-25; on Linux, reaching other processes’
/procentries is the approximationproc-other-processes, accepted by name), filter-bypassing kernel interfaces (R-28), nested user namespaces (R-29), Landlock rule loss (R-30), truncation without write access (R-31), seccomp listener hijack (R-32), setup that follows a planted link (R-33, Linux T1), service handoff over Unix sockets (R-34), case variants of an allow (R-37, macOS), and, with the proxy, an allowlist parser differential (R-35) and direct egress around the proxy (R-36). - Harness responsibility: trust handoff (R-07). procage denies writes to the known locations, and its handoff audit lists what a run created or changed; anything else the sandbox wrote must be treated as untrusted.
- Not applicable: helper replacement (R-26) concerns a helper program procage installs, and it installs none. A
Cage::exechelper is your own binary: keep it outside every writable path.
The catalog lists the evidence (test names) and kernel facts for each class; Kernel facts and testing describes how it is produced.
Reporting an escape#
Report a suspected sandbox escape privately through GitHub’s private vulnerability reporting. Do not open a public issue. Include the OS and version (macOS build or Linux kernel release), the procage version, and the smallest policy and program that show the problem. See the security policy.