Capabilities
A capability is a named piece of the boundary a tool runs inside: network, mount mode, browser, clipboard, PID namespace, GPU. The sandbox's default is the strict side of every one of them, and each capability is a deliberate, named relaxation.
Capabilities come from three sources, most specific first:
--capon the command line- the tool's entry in
kapsl.toml(or a trusted project.kapslrc) - the default the image itself declares
A capability is never inferred from what a tool appears to need. If the boundary you get is wrong, the fix is to name it.
The vocabulary
The complete --cap vocabulary, in the order the binary lists it:
| Cap | What it changes | Who may grant it |
|---|---|---|
net | Outbound network. Without it the container has a network namespace with no interface — not even DNS. --port can still publish a port into it. | declarable + CLI |
rw | The CWD mount becomes :rw (the default is :ro). | declarable + CLI |
ro | Forces the CWD mount read-only, overriding an image's own default that might be rw. | CLI only |
nomount | No CWD mount at all — the working directory is not readable, not just unwritable. Stricter than ro. | CLI only |
rwimg | The image's own filesystem is read-write. Advanced debugging: the tool can write into its own base image layers. | declarable + CLI |
browser | A FIFO URL relay plus a $BROWSER wrapper; when the tool opens a URL, the host opens it. | declarable + CLI |
clipboard | An OSC 52 shim at /kapsl-clipboard.sh plus the $KAPSL_CLIPBOARD variable — the copy mechanism an editor can find. | declarable + CLI |
pid | The host PID namespace instead of a private one (what htop shows you). | declarable + CLI |
gpu | GPU device passthrough via CDI. podman/docker only — a no-op on Apple's container runtime. | declarable + CLI |
netraw | CAP_NET_RAW inside the container's own namespace: raw sockets for ping, traceroute, tcpdump. | declarable + CLI |
nonet | Forces network off, even if the tool or a composed-in tool would auto-grant it. | CLI only |
nobrowser | Forces the browser relay off. | CLI only |
noclipboard | Forces the clipboard shim off. | CLI only |
nest | kapsl-in-kapsl: tools in this container can re-run kapsl through a brokered socket; each nested tool gets its own sandbox. Recursion stays bounded — a nested run never inherits nest. | CLI only |
--privileged is not a capability value; it is a separate flag that disables all capability restrictions and seccomp filtering at once.
Usage: comma- or space-separated, and the flag is repeatable.
$ kapsl --cap net,rw python script.py
$ kapsl --cap ro --cap nonet make testWhat each one means, concretely
net— with it, the container's network namespace gets an interface and outbound works; without it,curlfails before it can try. Publishing a port (-p) is the host's side of the same wall — the host can reach in even where the container can't reach out.rw/ro/nomount— the three CWD mount modes.rois the default posture (your tree is readable, not writable);rwlets the tool write to it;nomountremoves it from the container's view entirely. A tool that reads your sources but never writes them needs none of these — the read-only default already covers it.browser— the tool cannot open a real browser; it writes a URL into a FIFO, and the host side opens it in your actual browser. The tool sees$BROWSER; you see the tab.clipboard— mounts a shim an editor can find. OSC 52 is a sequence written to the terminal, and a tool with a terminal can write it either way. Withholding a terminal is what withholds that whole class.pid— with a private PID namespace, the tool sees only its own process tree;pidswaps in the host's, which is what makes a containerizedhtopshow your real system.gpu— CDI device passthrough. This is the one that is not just a namespace flag: it is a real attack-surface expansion, which is why granting it prints a warning.netraw— raw sockets, contained to the container's own namespace. This is the capability an image can declare for itself (it is image-label-driven), which is how a network tool ships with the one knob it actually needs.nest— the escape hatch for tools that themselves wrap other tools. Anything running in the container can start a nested kapsl run, so it is CLI-only by design: a tool's own metadata can never grant it to itself, and the nesting depth stays bounded because a nested run cannot carrynestinto the next level.
Negations and conflicts
The negation pattern is the same mechanism in reverse: a tool or a composed-in tool can auto-grant a capability, and nonet / nobrowser / noclipboard force it off anyway. Negations are mutually exclusive with their positive form, and the binary checks that at parse time:
$ kapsl --cap ro --cap rw jq '.a'
▍ ■ ERROR --cap: 'ro', 'rw', and 'nomount' are mutually exclusive — pick
▍ one
$ kapsl --cap nonet --port 8080:80 jq '.a'
▍ ■ ERROR --cap nonet conflicts with --port: a published port requires
▍ network access
Both exit 1 before anything runs. --cap gpu with --runtime container is likewise rejected — Apple's container runtime does not expose CDI GPU device passthrough.
What a tool may declare — and what only you may grant
The catalogue, [tools], and .kapslrc can declare a restricted set: net, rw, browser, clipboard, pid, gpu, rwimg, netraw. That is the set a signed tool can ask for, and it is checked against the field.
ro, nomount, the three negations, and nest are CLI-only. The split is the point: tightening (ro, nomount, the negations) is something you can do to any tool from the command line, and the one capability that changes what the container may start (nest) must never be grantable by a tool's own metadata. A tool can ask to be reached, to write, to show you things — it cannot ask to be a container runtime.
Which tools declare which
Two ways to find out before you run:
$ kapsl --info jq
CAPS ro
SECCOMP net-listen (no class declared) (tier: default)
and the index, which lists each tool's declared boundary at index.kapsl.sh.
Relaxation is explicit
Every escalation prints itself. --privileged says it disables all capability restrictions and seccomp filtering; --cap gpu says it is a real attack-surface expansion; --cap nest says anything running here can start another sandbox. If a run changed the boundary, the transcript says so — see Security Model for why that is a design requirement, and Exit Codes for what happens when a run stops instead.