>-
npx skills add https://github.com/docker/skills --skill docker-sandboxes-kits
A kit is a directory (or ZIP/OCI/git artifact) containing a spec.yaml
plus an optional files/ tree. sbx composes a kit into a running or
about-to-be-created sandbox at sbx create/sbx run --kit/sbx env time or
at sbx kit add time. This skill owns kit-spec v2 authoring, validation, and
distribution — everything under spec.yaml's own grammar — and defers what a
kit's declarations *mean at runtime* (credential injection, network
enforcement) to docker-sandboxes-network-credentials, and the sandboxes a
kit is composed into to docker-sandboxes-lifecycle.
Activate this skill when:
spec.yaml for a `kind:sandbox (complete agent) or kind: mixin` (extension) kit.
files to an existing built-in agent.
sign it, or verify a signature/provenance attestation.
error, or sbx kit add's recreate-aware requirement.
Do not use this skill when:
independent of the kit's own content — use docker-sandboxes-lifecycle.
does at runtime (proxy injection, allow/deny precedence, or what the
CURRENT network/global policy already permits), or is about secrets/
policy that have nothing to do with a kit — use
docker-sandboxes-network-credentials.
sbxenv.yaml file format that references kits via itsown kits: block — use docker-sandboxes-env for that file's schema
(this skill still owns what goes inside the referenced kit itself).
kind: sandbox vs kind: mixin — pick the right onekind: sandbox kit composes into any sandbox (a completeagent: base image + launch config). Any number of kind: mixin kits
layer onto it (tools, credentials, network, files). A mixin must not
declare a sandbox: block, extends:, or mixins:.
schemaVersion: "2" (the current clean grammar — nolegacy shims), kind, and name matching
^a-z0-9?$. Decoding is strict: any
unrecognized field anywhere is a hard error (e.g. a typo like
permissions.netwrok:), so a kit that validates has no silent typos.
schemaVersion: "2"
kind: mixin
name: extra-egress
apiKey.name or proxyManaged for the same service fails composition.
shell, docker-agent, and opencode already own github. An additive
routing-only entry (apiKey.inject, no name/proxyManaged/oauth, and
required: false) can extend the base credential instead. OAuth belongs
on sandbox kits, never mixins. See references/spec-v2-fields.md.
Inspect built-in definitions at sandboxlib/agentkits/agents/<agent>/spec.yaml
in the pinned source; sbx kit inspect takes artifact references, not
built-in names. Standalone mixin validation does not test composition.
sandbox: block (sandbox kits only)kind: sandbox (unless the kit extends: a parent thatalready supplies it); forbidden for kind: mixin.
image: is the pre-built base image. entrypoint: is the fixed processprefix (entrypoint[0] is the binary); command: is the mode-specific
argument tail — either a bare list (sets default, interactive falls
back to it) or {default: [...], interactive: [...]}.
For a complete minimal kit, use assets/spec-sandbox.yaml, which inherits
the embedded shell definition rather than inventing an image or command.
sandbox.build: (Dockerfile build) is **accepted but not built by theruntime this release** — a kit that sets build: must still set image:,
or it is rejected at load with an actionable error.
extends: (below) is the simplest way to get a real, working imagewithout inventing one.** A sandbox kit that extends a built-in agent
(e.g. extends: shell) inherits that agent's real sandbox.image and
may omit sandbox: entirely — see the minimal example asset, which does
exactly this rather than naming a made-up image reference.
permissions.network — and the all-egress-declared rulepermissions.network.allow/deny are the v2 home for what v1 spelled astop-level network:. Enforced shapes include exact host, exact host+port,
single-label wildcards (*.example.com), multi-label wildcards
(**.example.com), and CIDR prefixes. Port ranges are not supported by
the runtime matcher; use separate exact ports.
Deny wins within domain rules or within CIDR rules. A decisive domain
decision is evaluated before CIDR rules: an allowed hostname is not
checked against a CIDR deny for its resolved IP. Do not rely on a CIDR
deny alone to block an already-allowed hostname.
permissions:
network:
allow:
- registry.npmjs.org
deny:
- telemetry.example.com
permissions.network.allow is additive across a composition, and akit's own allow list is not the only thing granting a sandbox egress.**
The sandbox already carries the base agent's own allow list, plus
whatever the *global* or *per-sandbox* network policy (sbx policy,
independently of any kit) permits — see `docker-sandboxes-network-
credentials. **Removing a host from one kit's allow` list does not by
itself prove that host is blocked** — the global policy defaults
(balanced allows common package registries and AI services; allow-all
allows everything) or another composed kit may still permit it. Never
claim a host is blocked without checking the actual effective decision
with sbx policy check network --sandbox <name> <host> on a real
sandbox.
injection does not itself grant network access. Omitting an allow entry
leaves reachability dependent on the existing global/per-sandbox policy;
it does not necessarily block the host. Check the effective decision.
credentials — what the kit needs, never how the user stores itservice identity and where to inject theresolved value (apiKey and/or oauth); it never declares *how* the
user obtains or stores the credential — that lives in the user's own
bindings file, wired through sbx secret set (see
docker-sandboxes-network-credentials).
apiKey.inject[] needs a domain and either an explicit header+format (format must contain exactly one %s) or the scheme:
sugar: scheme: bearer expands to Authorization: Bearer %s (no
username), scheme: basic requires username and is mutually
exclusive with format. **Pick a service name no composed base agent
already declares** (see the duplicate-service rule above) — see
references/spec-v2-fields.md for a complete fragment.
apiKey.proxyManaged: true sets the in-container env var to the literalproxy-managed sentinel rather than leaving it unset; the real value is
substituted only by the proxy, on the allow-listed inject domains.
oauth needs tokenEndpoint.host/.path and, unlesspassthrough: true, non-empty sentinels.accessToken/.refreshToken.
passthrough: true is a security downgrade — the real token reaches
the container instead of a sentinel — use it only when the kit's own
design requires it and say so in description.
setup — install (once) vs. startup (every start) vs. files (startup-time writes)| Block | Command shape | Runs |
|---|---|---|
| setup.install[].command | string, via sh -c | Once, synchronously, before the agent first launches. Runs for every kit, built-in or not. |
| setup.startup[].command | list<string>, exec-style (no shell) | On every container start (create, stop/start, daemon restart, host reboot) — must be idempotent. |
| setup.files[] | file write via shell exec | At container startup; path absolute; only ${WORKDIR} placeholder allowed in content. |
Optional fragment for the shell kit in assets/spec-sandbox.yaml:
setup:
startup:
- command: ["sh", "-c", "mkdir -p ~/.my-kit"]
files:
- path: /home/agent/.my-kit/config.json
content: '{"workdir": "${WORKDIR}"}'
setup.files is not the same mechanism as the files/ directorytree (below).** setup.files entries are dynamic, ${WORKDIR}-
substituted writes performed at startup time; the files/home/ and
files/workspace/ directory tree is a set of static files packed
alongside spec.yaml and copied in at container-create time, and it is
specifically the files/workspace/ half of that tree — not
setup.files — that is written after the workspace is populated
(e.g. after an in-container git clone under --clone). Do not
conflate the two: setup.files has no "after workspace population"
timing guarantee of its own.
setup: lists concatenate in --kit order across composedkits.
user: "0") unless overridden;startup/entrypoint as the agent user (uid 1000) unless overridden.
Root install steps writing under /home/agent must chown it back to
agent:agent, or later agent-user writes there fail.
volumes — creation-time only, every volume must set a sizepath:, optional type: tmpfs (RAM-backed;omit/"" for the default block-backed volume), optional size:
(byte-size string) and mode: (octal).
sbx kit add (runtimeinjection) skips volume changes entirely; a kit that needs one must be
present at creation.
size: on a block volume. An unsized volume inherits a50 GiB default and costs real host disk immediately (ext4 inode-table
zeroing); 512 MiB is the practical floor — below it mke2fs switches
inode density and the space savings mostly disappear.
args — parameterizing a kitargs: (v2 only — the frozen v1 grammar has noargs block), each with exactly one of default/required: true, plus
optional description/enum/pattern. Reference with
${{ kit.args.NAME }} anywhere in spec.yaml or files/; substitution
happens before the spec is decoded. Every reference must be
declared, or loading fails — that is what makes the block a trustworthy
list of a kit's inputs. Quote a placeholder used in a string field
(VERSION: "${{ kit.args.version }}"), or an unquoted numeric-looking
value decodes as a number and fails to decode into a string field.
--kit-arg name=value (every kit) or--kit-arg kitname.name=value (one kit only), or --kit-args-file.
Never pass a secret this way — --kit-arg values are not masked; see
docker-sandboxes-network-credentials.
extends and mixins — composition, not runtime injectionextends: resolves only built-in agent names at this pinned release(shell, claude, etc.). Remote git/OCI parents fail to resolve, even
if pinned; the broader format specification is not an implementation
guarantee. The minimal asset uses the supported extends: shell.
mixins: is accepted with a warning but is not applied by this runtime.Use --kit or sbx kit add for composition. The format's immutable-ref
requirements do not make unimplemented remote inheritance work.
--kit and sbx kit add still accept mutable tags/branches; the CLI
parser does not enforce this recommendation.
requires.agent (mixin-only; rejected on kind: sandbox) pins thesingle base agent a mixin is designed for (e.g. Claude-specific env
vars). It is well-formedness-checked by the spec library; the actual
agent-affinity mismatch is enforced by the composition consumer, not by
sbx kit validate alone.
| Command | Purpose |
|---|---|
| sbx kit validate REFERENCE [--kit-arg ...] | Local directory, ZIP, or git reference; OCI is rejected. Schema-only well-formedness check. Never composes against a base agent — cannot catch a duplicate-service credential collision or confirm any domain is reachable at runtime. |
| sbx kit inspect REFERENCE [--kit-arg ...] [--json] | Loads and prints the decoded artifact before composing it, including --kit-arg substitution preview. |
| sbx kit pack DIRECTORY [-o OUTPUT.zip] | Packages a validated directory as a ZIP. |
| sbx kit pull REFERENCE [-o OUTPUT] | Pulls a kit's raw layer payload from an OCI registry without composing it. |
| sbx kit push DIRECTORY REGISTRY/REPO:TAG [--sign] | Packages and pushes; every push attaches an unsigned-by-default SLSA provenance attestation. |
| sbx kit provenance REFERENCE [--certificate-identity ...] | Prints the attestation push attached; marked UNSIGNED unless verified against a matching key/identity. |
| sbx kit sign REFERENCE / sbx kit verify REFERENCE | Sigstore sign/verify (keyless by default); prefer --identity-token-file over --identity-token. |
| sbx kit add SANDBOX REFERENCE [--kit-arg ...] | Injects a mixin only into an existing sandbox at runtime (recreate-aware label required); container-immutable settings (security.privileged, volumes:) cannot take effect this way. |
See references/kit-distribution-commands.md for full flag lists and
worked examples of each command above.
sbx create/run --kit,sbx kit add SANDBOX), use docker-sandboxes-lifecycle.
credentials:/permissions.network: declarations meanat runtime — proxy injection, allow/deny precedence, the effective
policy a sandbox actually has once global/per-sandbox policy is
included, where the user stores the actual secret value — use
docker-sandboxes-network-credentials.
sbxenv.yaml file whose kits:/agent: fields reference a kitby this schema, use docker-sandboxes-env.
references/sources.md — provenance for every rule above (spec package, SPEC-v2.md, help captures, docs URLs).references/spec-v2-fields.md — the complete v2 field table (common fields, sandbox-only fields, mixin-only fields, shared blocks) for lookup without re-reading the full spec.references/kit-distribution-commands.md — full flags and worked examples for sbx kit validate/inspect/pack/pull/push/provenance/sign/verify/add.assets/spec-sandbox.yaml — a genuine minimal kind: sandbox kit thatextends: shell to inherit a real, working image rather than inventing
one.
assets/spec-mixin.yaml — a genuine minimal kind: mixin kit with nocredentials at all (an egress-only extension), which composes cleanly
with every built-in agent.
checks/verification.md — Schema, composition, egress, and kit-add checks (unexecuted integration runbook; isolated --app-name, no registry publishing or signing).Take docker/docker-sandboxes-kits from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.