Security Configuration

Security in boks is controlled per-invocation by command-line flags. There is no persistent security configuration beyond the defaults — every container starts from the most restrictive profile and you grant only what a given command needs.

Default posture

Every container launches with network off, working directory read-only, all capabilities dropped, and no-new-privileges set. See Security Layers for the full table and the exact Podman flags behind each.

Per-invocation overrides

FlagWhat it enables
--cap netFull network access
--cap nonetForce network off, even if the tool's own default (or a composed tool) would otherwise grant it. Mutually exclusive with --cap net and with --port
--port <spec>Publish a container port to the host (implies --cap net)
--cap rwWrite access to the working directory
--cap roForce read-only working directory (overrides image metadata)
--cap nomountSkip mounting the working directory at all — tighter than ro (not just unwritable but unreadable)
--cap rwimgWrite access to the image filesystem
--cap nobrowserForce the browser relay off, even if the tool's own default would otherwise grant it. Mutually exclusive with --cap browser
--cap noclipboardForce the clipboard relay off, even if the tool's own default would otherwise grant it. Mutually exclusive with --cap clipboard
--privilegedRemoves capability restrictions and no-new-privileges
--skip-scanSkip the vulnerability scan for this run
--force-scanForce a fresh scan even if a cached result is valid

These apply only to the single invocation. They do not change defaults or persist to future runs.

Running as root

Native Linux only. If boks's own process is already running as real root there — sudo <tool>, an already-root shell, su -c, a root cron job — the container's process runs as uid 0 too, matching what a plain (non-boks) command run the same way would already do. This is detected automatically (there is no --sudo/--root flag; there's nothing to type), and it covers --installed shim symlinks the same way: sudo rg ... runs rg as root inside the container even though boks is never typed.

This changes only the uid. Network stays off, the working directory stays read-only, and capabilities stay dropped, all exactly as they would without root — running as root locally never implied open network either, so boks doesn't invent that implication. --cap net, --cap rw, --cap rwimg, and --privileged remain the explicit flags for those, and combine with root normally (e.g. sudo boks --cap rwimg <tool> is root plus a writable image filesystem, same as --cap rwimg already gives any other invocation).

On macOS this has no effect, and boks says so rather than silently doing nothing: podman/docker there are clients to a separate Linux VM (podman machine), which the host process's own uid has no bearing on — forcing -u 0 would only make the container claim to be root (id -u prints 0) with no corresponding access to anything real. Rather than manufacture that illusion — the exact thing that would trip someone expecting sudo boks vim /etc/resolv.conf to edit a real system file — sudo boks <tool> on macOS runs identically to a plain, non-sudo invocation, and prints a one-line note explaining why. This is why the file-editing example above doesn't work anywhere, sudo or not: boks containers only ever see the working directory and explicit mounts, never arbitrary host paths.

Local podman/docker only — --k8s pods run under a completely separate identity governed by the cluster's own RBAC/securityContext, unrelated to the local client process's OS privilege, so this has no effect there either.

Two things below hold on both platforms, independent of whether the container's own uid changes — they're driven by boks's own process being real root, not by anything the container does:

sudo boks <tool> restores the invoking user's own $HOME for the duration of the run (looked up via SUDO_USER) — verified live on both platforms that a bare sudo resets $HOME to root's own by default (Ubuntu/Debian's standard sudoers env_reset, and macOS's own default policy both do this). Without the restore, boks's own config/dotfile lookups (~/.config/boks/boks.toml, ~/.gitconfig, ~/.ssh) and, on macOS, podman machine's own connection selection (which VM backend it talks to) would all silently point at root's own home instead.

boks's own bookkeeping (the scan cache, tool-image freshness stamps, the cached tool index) does not follow that restored $HOME, though — root gets its own, separate cache under root's real home (/root/.cache/boks on Linux, /var/root/.cache/boks on macOS) rather than writing into the invoking user's directory. This is deliberate: it means a sudo boks <tool> run never creates a file in the user's own directory that a later, non-root run can't overwrite — no ownership conflict to work around, at the cost of root and non-root runs not sharing these caches (a sudo'd scan gets re-verified by a later plain run and vice versa — an occasional extra scan or pull, not a correctness problem). The one deliberate exception, macOS only, is the vulnerability scanner's own exported image tarball and its grype cache directory, which must stay in the shared, invoking-user location because they get bind-mounted into a separate scanner container and podman machine's virtiofs sharing can't reach root's own home — boks explicitly (and audibly, via a log line) hands ownership of just those two paths to the invoking user so that mount can succeed. This chown never happens on Linux: rootful podman there can read/mount any file regardless of ownership, so there's nothing to work around.

On native Linux, rootful podman (what a root process talks to) uses entirely separate image storage from your normal rootless one — verified live: the first sudo boks <tool> run re-pulled an image that was already cached for the plain, non-root invocation. This is inherent to how podman itself separates rootful and rootless storage, not something boks can avoid; expect one extra pull the first time you run a given tool under root on a given machine.

Scan policy: deny / prompt / notify

boks scans every image involved in a run — the primary tool, an ad-hoc -e environment, and every composed dependency — and makes one combined decision covering all of them, not one prompt per tool (a plain boks emacs, which composes bash/coreutils underneath, used to be able to interrupt you twice; now it's a single summary and, at most, a single question). Findings are sorted into three bands using two thresholds from boks.toml (each one of critical, high, medium, low, never):

[global]
security_scan_deny   = "never"   # findings at/above this abort the run outright
security_scan_prompt = "high"    # at/above this (below deny): review prompt — already covers CRITICAL too
  • Deny (default never) — the run aborts outright, no review possible. boks does not set this by default: you asked for a tool explicitly, so boks shouldn't refuse to run it on your behalf. Set security_scan_deny = "critical" (or lower) to restore a hard block for CI or a locked-down environment.

  • Prompt (default high — its "at or above" semantics already include critical, so this one setting covers both by default) — boks prints a single-line, severity-colored summary and asks:

     ▍  WARNING    SCAN: Critical: 1, High: 2, Medium: 15, Low: 7, Unknown: 0. Continue? y/N/d (details)

    y proceeds, d shows the full breakdown (grouped by severity, each CVE tagged with which tool it affects, with a clickable advisory link) and asks again, anything else declines. Accepting caches each scanned image's findings for its exact digest, so warm runs don't re-ask until an image changes or the cache entry expires; declining leaves nothing cached, so the next run asks again. Without a terminal to ask on (CI, piped stdio, --non-interactive) the run fails closed — a required review that can't happen is a failure. CI setups should either pass --skip-scan deliberately or set security_scan_prompt = "never".

  • Notify — anything found below the prompt threshold prints the same summary line at INFO level instead of WARNING — silent by default (boks "handles" security without asking you to think about it), visible with --verbose.

Set both to never to reduce the scan to purely informational (still visible under --verbose, never blocking). Old image versions (tool@version lines that upstream no longer maintains) commonly carry open CVEs — see the tool index on version aging for why boks keeps them anyway.

Image-defined defaults

A tool's container image can declare security defaults via OCI labels (e.g. sh.boks.security.network=true, sh.boks.security.fs=rw, sh.boks.ports=8888). When present, boks uses them as the starting profile — unless you override with an explicit --cap net/--cap rw/--cap ro/--port, or force it off with --cap nonet/--cap nobrowser/--cap noclipboard. An explicit flag always wins, in either direction. Declared ports imply network, exactly like the --port flag; this is how boks jupyter publishes its notebook server without any flags — and --cap nonet is how you'd refuse that grant even for a tool that declares it wants network by default.

Project-defined overrides

A tool's own image can declare per-subcommand default args and ports (see Tool Mappings → Image-declared args and ports) — this is how boks jupyter lab auto-binds 0.0.0.0 and publishes 8888, or boks zola serve auto-binds 0.0.0.0 and publishes 1111, with no project config. A project's .boksrc can override those defaults for one specific subcommand — e.g. when the image's port conflicts with something else on the host, or you want loopback instead of all interfaces:

# .boksrc — override the image's default for this project only
tools:
  zola:
    subcommands:
      serve:
        ports: ["2222"]                       # publish 2222 instead of the image's 1111
        args: ["--interface", "127.0.0.1"]    # bind loopback instead of 0.0.0.0

ports publishes a port and implies network, exactly like the image-label mechanism above, but only for the named subcommand (zola build/zola check are unaffected — publishing unconditionally would hand them network access they never asked for). Project overlay ports replace the image's own default ports (a different port is a different server, not additive); project overlay args append to the image's own default args, so your own flag lands last and wins under last-wins parsing. Both go through the same trust-on-first-use review as every other .boksrc declaration — see Project Config for the full field reference and merge semantics.

Shim security

When a tool is invoked via a shim (e.g. rgboks), it runs with the default security profile — no network, read-only filesystem. To pass flags through a shim, use the full boks --cap net rg ... form, or invoke boks directly.