How It Works
The sandbox engine is the core of boks. It translates your command — boks rg "TODO" . — into a container invocation with the right security settings, mounts, and networking, then hands off to the container runtime.
Execution flow
When you run boks rg "TODO" ., boks:
- Resolves
rgto a container image (localboks.toml→ cached index → fallback). See Tool Index. - Pulls the image if it is not in the local cache, and best-effort re-pulls a locally-cached floating tag past its freshness window (first run only for a stable pin; see Core Concepts → Always up to date).
- Scans the image for known vulnerabilities, unless a still-valid cached verdict already exists — see Vulnerability scanning below.
- If the image is composable, mounts its files into a shared base image at the path it declares, instead of running it directly. If it's a standalone image, runs it as-is. See Composable images below.
- Determines the working directory and builds the mount configuration (see Security Layers).
- Applies the security profile — network mode, filesystem mode, capabilities, scan settings.
- Rewrites any absolute path arguments that point inside the working directory to
/boks/workdir/<relative>(so shebang scripts that reference$PWD/filestill work). - Invokes the container runtime.
For a normal run, boks replaces its own process with the container runtime via exec() — no boks process stays alive while the tool runs, so signals and exit codes pass straight through. For --watch mode, boks uses spawn() instead so it can stay alive, monitor the env file, and restart the container on change.
Composable images
Most images in the tool index aren't standalone containers — they're built to a composable contract: a single binary, plus the exact shared-library closure it needs, staged under a versioned path like /boks/dist/rg-14.1.1/. At run time, boks doesn't run that image directly against its own root. It mounts the image's files into a small, shared base image (boks-base-static — timezone data, a minimal passwd/group, the TLS trust store — nothing tool-specific) at the path the image itself declares, then execs the binary there.
This is what makes composition (-e) possible: boks -e ripgrep,jq bash mounts rg's image and jq's image into the same bash container, each at its own versioned path, rather than needing a purpose-built "bash with rg and jq" image built in advance. To make every composed binary reachable on PATH without hand-listing each mount path, boks assembles a small symlink farm on the fly — one symlink per composed tool, bind-mounted read-only at /boks/bin, the single entry prepended to PATH regardless of how many tools are composed.
Tool groups name a set of composed tools under one name so you don't have to spell them out every time — the built-in coreutils group (ls/cp/rm/sed/... as one name) is why a bare boks bash script.sh already has a working shell environment with no -e at all. Declare your own in boks.toml's [groups] — see Tool Mappings → Tool groups.
Each composable binary also carries its own seccomp filter, independent of and underneath the container-wide security profile below — see Security Layers → Per-binary confinement for the mechanism, and specifically why it matters for composition: a capability granted to the container for one composed tool's sake (e.g. network, for dig) doesn't silently extend to every other tool mounted into that same container.
Not every tool in the index has been converted to this shape yet — a few still ship as an ordinary standalone image (their own full container, not mounted into the shared base). The distinction is invisible from the command line either way; boks <tool> and composition work the same regardless of which shape a given image is, and composable-only tools can't be named in an -e compose segment.
Vulnerability scanning
Right after an image is resolved and pulled, boks scans it for known vulnerabilities before ever running it. A composable image is built FROM scratch — it has none of a full Linux distribution's package database for a generic scanner to key off — so instead of scanning the filesystem blind, boks reads a software bill of materials the image itself ships (the sh.boks.sbom label, a CycloneDX document listing every package and library the image actually contains) and matches that against the vulnerability database. This gives a FROM scratch image the same scan depth as a full distro image would get, driven by a manifest instead of guessing from installed files.
Findings are sorted into deny/prompt/notify bands you control in boks.toml — a CRITICAL finding blocks the run by default. See Security Configuration → Scan policy for the thresholds and what each band does.
Container runtimes
The engine abstracts over different container runtimes via a single ContainerRuntime trait. Each runtime reports which security features it supports:
| Runtime | How to select | Supports |
|---|---|---|
| Podman (default) | automatic | read-only FS, cap-drop, security-opt, network isolation, keep-id user mapping |
| Docker | automatic fallback, or --runtime docker | read-only FS, cap-drop, security-opt, network isolation |
When a runtime doesn't support a feature, boks omits the corresponding flag rather than failing. See Security Layers.
Shim mode
When boks is invoked under a different name (e.g. rg via a symlink at ~/.local/bin/rg), the engine reads argv[0] to determine the tool name and proceeds identically to the boks rg case. This is how installed shims work invisibly.
Environment building
When you request packages (python@3.12:numpy or -e @pip:requirements.txt), boks builds a derived image on top of the base: it generates a Dockerfile, runs podman build with a content-hash tag, and caches the result. Subsequent runs with the same inputs skip the build. See Environments and Environment Caching.