The one place Docker container hardening defaults are defined.
Both CrowdControl.Backend.Docker (which runs the CLI directly, over a FIFO
and a tee file) and CrowdControl.Provider.Docker (which runs sandboxd
and talks HTTP to it) create containers, and both must create them equally
hardened. Two copies of these defaults would drift, and the failure mode is
silent: a sandbox that quietly lost CapDrop: ALL looks and behaves exactly
like one that did not.
It lives in the Backend.Docker.* namespace rather than under Provider
because it is Docker Engine API vocabulary, and because that is already where
CrowdControl.Backend.Docker.API lives — which every Docker-shaped provider
also calls. One namespace for "things that speak to the Engine API".
What is a default and what is opt-in
On by default, because the code running in a sandbox is model-driven and untrusted and none of these breaks an ordinary CLI:
CapDrop: ["ALL"]— a CLI needs no Linux capabilities.SecurityOpt: ["no-new-privileges:true"]— it never needs to gain any.PidsLimit: 512—MemoryandNanoCpusdo not bound PIDs, so the fork-bomb ceiling has to be set separately.RestartPolicy: "no"— non-negotiable. A restarted container truncates the capture/tee file and invalidates every persistedbyte_offset. Making restart impossible is cheaper and safer than detecting it.
Opt-in, because both genuinely break images that expect otherwise:
:readonly_rootfs— breaks any CLI that writes outside the tmpfs mounts (npm caches,~/.claude, and so on).:user— breaks images that expect root. Applied by the caller, since it is a container-level rather than a host-config field.
Summary
Functions
Build a Docker HostConfig object from caller options.
The tmpfs mounts used when :readonly_rootfs is enabled.
The hardening defaults, as a map, with no network or resource limits.
Functions
Build a Docker HostConfig object from caller options.
:network_mode is required and has no default here on purpose: the two call
sites want genuinely different networks ("none" for the Docker backend, a
per-sandbox bridge for the Docker provider, which needs a publishable port),
and a default would let one of them inherit the other's posture silently.
Recognised keys in config: :cap_drop, :security_opt, :pids_limit,
:cpus, :memory, :readonly_rootfs, :tmpfs.
@spec default_tmpfs() :: map()
The tmpfs mounts used when :readonly_rootfs is enabled.
@spec hardening_defaults() :: map()
The hardening defaults, as a map, with no network or resource limits.
Exists so a test can assert that every call site produces the same hardening without having to know either call site's network posture.