Core Concepts

The problem with installed tools

Every tool you install on your host — python, node, rg, curl — has full access to your user account. It can read your SSH keys, scan your home directory, and reach any host on the network. Most of the time that's fine. But it only takes one compromised package or one subtle supply chain attack for that to be a serious problem.

Beyond security, installed tools accumulate. Versions conflict. Different projects need different Python versions. Your PATH fills up. A system update breaks something. Managing this is real, ongoing work.

The boks approach

boks treats every tool as an ephemeral container. When you run boks jq '.' data.json:

  • jq runs inside a minimal OCI container
  • Your current directory is mounted read-only as /boks/workdir
  • The container has no network access
  • When jq exits, the container is deleted — nothing persists

The tool sees only your working directory. It cannot read ~/.ssh. It cannot connect to the internet. It cannot write to your filesystem unless you explicitly grant those permissions. And when it's done, it's gone.

Principles

Three principles drive every design decision in boks. They are ordered — when they conflict, the earlier one wins.

  1. Secure, first. Security is the first priority, and it covers the tool and the packages it pulls in. boks scans the built environment image, not just the base, so a vulnerable transitive dependency is in scope — unlike a traditional package manager, which runs whatever pip install resolved without a second look. When a scan fails, the run is blocked.

  2. "Just work." boks reads the user's intent from the explicitly named tool, version, and packages — boks python@3.12:requests should run a Python 3.12 interpreter with requests available, no virtualenv, no pip install, no PATH gymnastics. The user names what they want to run; boks handles the infrastructure to make it run.

  3. Always up to date, automatically. There is no update or upgrade command. A tool or env named with a floating tag (python:3.13, nmap:latest) is expected to stay current on its own: boks re-checks the registry on a freshness window and best-effort re-pulls before a run. Pin a digest (tool@sha256:…) to opt out of automatic currency in exchange for exact reproducibility.

The freshness window, the scan-on-failure retry, the env-base freshness loop, and the zero-trust defaults below are all consequences of these three. The sections that follow expand on how each is realized.

Zero-trust defaults

Every container boks launches starts with the most restrictive profile possible:

CapabilityDefaultOverride
Network accessdenied (--network=none)--cap net
Filesystem writesdenied (mounted :ro)--cap rw
Linux capabilitiesdropped (--cap-drop=ALL)--privileged
New privilegesdenied (no-new-privileges)--privileged
Container is read-onlyyes (--read-only)--cap rwimg

You choose to grant access when you need it. The default assumption is that the tool should not be trusted with anything beyond your current directory.

Images from the index

boks doesn't point at vendor images — not Docker Hub's official language images, not a third-party tool's own container, not docker.io in any form. It maintains a tool index — a mapping from tool names to images boks builds and publishes itself, e.g. python maps to boks.sh/python, rebuilt regularly and kept clean enough to pass boks's own vulnerability scan (the official Docker Hub language images routinely carry open CRITICAL CVEs that nobody upstream is racing to fix).

Most of these images are also composable: instead of a full standalone container, an image is a single binary — plus the exact shared-library closure it needs — staged to mount into a shared, minimal base at run time. boks rg "TODO" . doesn't run a whole rg operating system image; it mounts just rg's own files into a blank base and execs it there. This is also what lets you pull more than one tool into the same container (boks -e ripgrep,jq bash composes rg and jq into one bash session) without dragging in two separate OS layers. Composition is covered in more depth in How It Works.

Each composable binary also carries its own seccomp filter — baked in at build time, confining that specific binary to the syscalls it actually needs (write to stdout, open an outbound connection, fork a subprocess, ...) regardless of what else is composed into the same container. This is a second, independent layer underneath the container-wide restrictions below: even if dig is composed into a container that has network access for its own sake, dig's neighbor in that same container doesn't inherit it just by being there. See Security Layers for the mechanism.

You can still override any mapping in your local boks.toml config, pin specific versions, or point at images from any registry — that's your own tool mapping, independent of what the index ships.

Always up to date

boks has no update or upgrade command. A tool named without a pinned digest is expected to stay current on its own. Tags like python:3.13 and nmap:latest float forward on every upstream rebuild, so boks re-checks the registry for a cached image older than a freshness window (default 7 days) and best-effort re-pulls it before a run.

This is a convenience, not a blocker: if the refresh pull fails (no network, registry down), boks uses the cached image and continues. The vulnerability scan is the security backstop, not the freshness window. And if a scan fails on a floating tag, boks gives it one more chance — re-pulling the newest version and re-scanning, since a rebuild may have fixed the issue.

For exact reproducibility instead of currency, pin a digest (tool@sha256:… in boks.toml) — digest-pinned images are never refreshed. See Configuration → Freshness for the image_max_age_secs knob.

Shims

For tools you use constantly, typing boks every time is friction. boks --install rg creates a symlink at ~/.local/bin/rg that points to the boks binary. When you run rg "TODO" ., your shell calls that symlink, boks reads argv[0] to learn the tool name is rg, and runs it in a container — invisibly. You get the full boks security model with zero extra typing.

Language environments

boks extends the same model to language runtimes and their package ecosystems. boks python@3.12:requests,jinja2 builds a container image with those packages installed, caches it by content hash, and runs it. The same syntax works as a shebang:

#!/usr/bin/env -S boks python@3.12:requests,jinja2

import requests
# ...

Anyone with boks installed can run this script. No virtualenvs, no pip install, no version mismatches.