Threat model
This page summarizes SECURITY.md,
which is the authoritative and more detailed version.
The attacker
Section titled “The attacker”The attacker controls everything a request can carry: commands, arguments, environment variables, stdin, file names and contents, working directories, source code and the binaries it produces. They may try to:
- escape the workspace through path traversal, symlinks or races;
- exhaust memory, processes, CPU, disk or output;
- escape the namespaces or regain privileges;
- observe or interfere with other tasks and requests;
- leave something running after the request ends.
What is trusted
Section titled “What is trusted”The host kernel, the Docker image, the Faber service, the cgroup hierarchy, the installed toolchains and the operator. Faber does not claim to mitigate kernel vulnerabilities or every microarchitectural side channel; see the isolation tier.
Invariants
Section titled “Invariants”An invariant counts as verified only when it has an implementation review and an executable acceptance test. The presence of a namespace flag is not evidence.
| Area | Invariant |
|---|---|
| Workspace files | Submitted paths become regular files under /faber, created one component at a time without following symlinks or magic links and without crossing mounts. Symlink swaps and RENAME_EXCHANGE races cannot redirect writes |
| Root filesystem | The old root is detached, propagation is private, toolchains are read-only, writable mounts are nodev,nosuid, /sys is an empty read-only tmpfs |
Processes and /proc | Every task has its own PID namespace; host processes and other tasks are invisible and cannot be signaled. procfs is mounted with subset=pid. Orphans are reaped |
| Network | No host or external connectivity over IPv4 or IPv6, no resolver configuration, no network namespace reused between runtimes |
| Identity | Each task’s user namespace maps only 65534 to one host UID/GID leased to its request; concurrent requests get different ones. No supplementary groups |
| Privileges | Capabilities, identity changes, namespace map rewrites, chroot, hostname changes and device access all fail; no setup file descriptors survive exec |
| Syscalls | A versioned seccomp filter is installed before exec. Denied calls kill the task with SIGSYS; under v2 profiles unlisted calls fail with ENOSYS |
| Memory and processes | The whole task tree is held to memory.max and pids.max; a task’s own OOM is told apart from an ancestor’s |
| CPU and rlimits | CPU bandwidth, CPU time and wall time are bounded independently; file size, descriptors, stack and core dumps have finite limits |
| Output | stdin, stdout and stderr are drained concurrently; each stream and the whole request are bounded |
| Request scoping | Each request has its own cgroup subtree; one request’s deadline kill or memory pressure cannot pick a victim in another |
| Admission | A client disconnect neither frees its slots early nor leaves its sandbox running |
| Cleanup | No process, cgroup or sandbox root survives success, failure, timeout, OOM, policy kill, cancellation, shutdown or partial setup |
Each request is run by a jailer: the service binary started again as a fresh single-threaded process that receives only the request. The jailer and the per-task supervisors hold none of the service’s memory, environment or file descriptors (in particular, not the API key), run in the request’s cgroup with a fixed allowance, and die with the service.
How it is verified
Section titled “How it is verified”- The sandbox acceptance and adversarial suites run on every push and pull request, in privileged Docker on disposable CI virtual machines, on both x86_64 and ARM64, and are repeated several rounds to shake out races.
- A security probe (
tests/fixtures/security_probe.c) records the kernel state from inside a task: identity maps, groups, capabilities,NoNewPrivs, seccomp mode, mounts, routes, rlimits and cgroup membership. - Developers run privileged tests only inside a disposable VM
(
scripts/vm.sh), never on their own kernel.
The test matrix lists the attack families covered.
Known gaps
Section titled “Known gaps”- The
v2allowlists are derived from traces of twelve languages and the shell; a legitimate syscall they do not list fails withENOSYS. Thev1denylists allow everything not known to be dangerous. - Sandbox identities are leased per process; several Faber instances on one
kernel overlap unless each has its own
SANDBOX_IDENTITY_BASE. - One API key scope: no tenants or per-key quotas. Rate limits are per key and kept by each instance separately.
- Namespace isolation cannot prevent kernel vulnerabilities, all denial-of-service against the shared kernel, or microarchitectural attacks.
Reporting a vulnerability
Section titled “Reporting a vulnerability”Do not open a public issue. Contact the maintainer privately with the affected revision, reproduction steps, the invariant you expected to hold, the kernel evidence you observed, and the potential impact.