docker-sandboxes-kits
Use this skill when authoring, validating, packaging, signing, or composing a Docker Sandboxes kit `spec.yaml` (`sbx kit add/inspect/pack/pull/push/sign/validate/verify`), even if the user just says they want to "add a tool to a sandbox agent", "build a reusable sandbox extension
Install
npx skills add https://github.com/docker/skills/tree/main/skills/docker-sandboxes-kits
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install docker-skills@llmmart
git clone https://github.com/docker/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole docker/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Docker Sandboxes: Kits (spec.yaml)
Overview
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.
When to use this skill
Activate this skill when:
- The user wants to write, validate, or pack a
spec.yamlfor akind: sandbox(complete agent) orkind: mixin(extension) kit. - The user wants a mixin to add a tool, credential, network allowance, or files to an existing built-in agent.
- The user wants to publish a kit to (or pull one from) an OCI registry, sign it, or verify a signature/provenance attestation.
- The user is debugging a kit-validation error, an argument-substitution
error, or
sbx kit add's recreate-aware requirement.
Do not use this skill when
Do not use this skill when:
- The task is creating/running/removing the sandbox a kit is composed into,
independent of the kit's own content — use
docker-sandboxes-lifecycle. - The task is what a credential or network rule a kit declares actually
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. - The task is the
sbxenv.yamlfile format that references kits via its ownkits:block — usedocker-sandboxes-envfor that file's schema (this skill still owns what goes inside the referenced kit itself).
Core guidance
kind: sandbox vs kind: mixin — pick the right one
- Exactly one
kind: sandboxkit composes into any sandbox (a complete agent: base image + launch config). Any number ofkind: mixinkits layer onto it (tools, credentials, network, files). A mixin must not declare asandbox:block,extends:, ormixins:. - Every kit needs
schemaVersion: "2"(the current clean grammar — no legacy shims),kind, andnamematching^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$. Decoding is strict: any unrecognized field anywhere is a hard error (e.g. a typo likepermissions.netwrok:), so a kit that validates has no silent typos.schemaVersion: "2" kind: mixin name: extra-egress - Do not redefine a base agent's credential in a mixin: declaring a new
apiKey.nameorproxyManagedfor the same service fails composition.shell,docker-agent, andopencodealready owngithub. An additive routing-only entry (apiKey.inject, no name/proxyManaged/oauth, andrequired: false) can extend the base credential instead. OAuth belongs on sandbox kits, never mixins. Seereferences/spec-v2-fields.md. Inspect built-in definitions atsandboxlib/agentkits/agents/<agent>/spec.yamlin the pinned source;sbx kit inspecttakes artifact references, not built-in names. Standalone mixin validation does not test composition.
The sandbox: block (sandbox kits only)
- Required for
kind: sandbox(unless the kitextends:a parent that already supplies it); forbidden forkind: mixin. image:is the pre-built base image.entrypoint:is the fixed process prefix (entrypoint[0]is the binary);command:is the mode-specific argument tail — either a bare list (setsdefault,interactivefalls back to it) or{default: [...], interactive: [...]}. For a complete minimal kit, useassets/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 the runtime this release — a kit that setsbuild:must still setimage:, or it is rejected at load with an actionable error.extends:(below) is the simplest way to get a real, working image without inventing one. A sandbox kit that extends a built-in agent (e.g.extends: shell) inherits that agent's realsandbox.imageand may omitsandbox:entirely — see the minimal example asset, which does exactly this rather than naming a made-up image reference.
Egress: permissions.network — and the all-egress-declared rule
permissions.network.allow/denyare the v2 home for what v1 spelled as top-levelnetwork:. 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.compermissions.network.allowis additive across a composition, and a kit'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 — seedocker-sandboxes-network- credentials. Removing a host from one kit'sallowlist does not by itself prove that host is blocked — the global policy defaults (balancedallows common package registries and AI services;allow-allallows everything) or another composed kit may still permit it. Never claim a host is blocked without checking the actual effective decision withsbx policy check network --sandbox <name> <host>on a real sandbox.- Declare the egress a kit requires explicitly for reproducibility. Credential 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 it
- Each entry declares a
serviceidentity and where to inject the resolved value (apiKeyand/oroauth); it never declares how the user obtains or stores the credential — that lives in the user's own bindings file, wired throughsbx secret set(seedocker-sandboxes-network-credentials). apiKey.inject[]needs adomainand either an explicitheader+format(formatmust contain exactly one%s) or thescheme:sugar:scheme: bearerexpands toAuthorization: Bearer %s(nousername),scheme: basicrequiresusernameand is mutually exclusive withformat. Pick aservicename no composed base agent already declares (see the duplicate-service rule above) — seereferences/spec-v2-fields.mdfor a complete fragment.apiKey.proxyManaged: truesets the in-container env var to the literalproxy-managedsentinel rather than leaving it unset; the real value is substituted only by the proxy, on the allow-listed inject domains.oauthneedstokenEndpoint.host/.pathand, unlesspassthrough: true, non-emptysentinels.accessToken/.refreshToken.passthrough: trueis 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 indescription.
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, 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.filesis not the same mechanism as thefiles/directory tree (below).setup.filesentries are dynamic,${WORKDIR}- substituted writes performed at startup time; thefiles/home/andfiles/workspace/directory tree is a set of static files packed alongsidespec.yamland copied in at container-create time, and it is specifically thefiles/workspace/half of that tree — notsetup.files— that is written after the workspace is populated (e.g. after an in-containergit cloneunder--clone). Do not conflate the two:setup.fileshas no "after workspace population" timing guarantee of its own.- All three
setup:lists concatenate in--kitorder across composed kits. - Default execution users: install as root (
user: "0") unless overridden; startup/entrypoint as the agent user (uid1000) unless overridden. Root install steps writing under/home/agentmustchownit back toagent:agent, or later agent-user writes there fail.
volumes — creation-time only, every volume must set a size
- Each entry needs an absolute
path:, optionaltype: tmpfs(RAM-backed; omit/""for the default block-backed volume), optionalsize:(byte-size string) andmode:(octal). - Volumes apply only at sandbox-create time —
sbx kit add(runtime injection) skips volume changes entirely; a kit that needs one must be present at creation. - Always set
size:on a block volume. An unsized volume inherits a 50 GiB default and costs real host disk immediately (ext4 inode-table zeroing); 512 MiB is the practical floor — below itmke2fsswitches inode density and the space savings mostly disappear.
args — parameterizing a kit
- Declare under top-level
args:(v2 only — the frozen v1 grammar has noargsblock), each with exactly one ofdefault/required: true, plus optionaldescription/enum/pattern. Reference with${{ kit.args.NAME }}anywhere inspec.yamlorfiles/; 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. - Supply values with
--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-argvalues are not masked; seedocker-sandboxes-network-credentials.
extends and mixins — composition, not runtime injection
extends: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 supportedextends: shell.mixins:is accepted with a warning but is not applied by this runtime. Use--kitorsbx kit addfor composition. The format's immutable-ref requirements do not make unimplemented remote inheritance work.- Prefer digest/commit-pinned CLI kit references for reproducibility.
--kitandsbx kit addstill accept mutable tags/branches; the CLI parser does not enforce this recommendation. requires.agent(mixin-only; rejected onkind: sandbox) pins the single 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 bysbx kit validatealone.
Validating, packaging, and distributing
| 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.
Related skills
- For the sandboxes a kit is composed into (
sbx create/run --kit,sbx kit add SANDBOX), usedocker-sandboxes-lifecycle. - For what a kit's
credentials:/permissions.network:declarations mean at 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 — usedocker-sandboxes-network-credentials. - For the
sbxenv.yamlfile whosekits:/agent:fields reference a kit by this schema, usedocker-sandboxes-env.
References
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 forsbx kit validate/inspect/pack/pull/push/provenance/sign/verify/add.
Assets
assets/spec-sandbox.yaml— a genuine minimalkind: sandboxkit thatextends: shellto inherit a real, working image rather than inventing one.assets/spec-mixin.yaml— a genuine minimalkind: mixinkit with no credentials at all (an egress-only extension), which composes cleanly with every built-in agent.
Checks
checks/verification.md— Schema, composition, egress, and kit-add checks (unexecuted integration runbook; isolated--app-name, no registry publishing or signing).
Files (skills)
-
agents
-
openai.yaml 382 B
interface: display_name: 'Docker Sandboxes: Kits (spec.yaml)' short_description: Author, validate, package, sign, and compose reusable sandbox/mixin kits (spec.yaml, schema v2). Experimental. default_prompt: Use this skill when authoring, validating, packaging, signing, or composing a Docker Sandboxes kit spec.yaml (`sbx kit ...`). policy: allow_implicit_invocation: true
-
-
assets
-
spec-mixin.yaml 449 B
# Complete mixin; save as spec.yaml in its own directory. # Adds no credentials, avoiding duplicate-service definitions. schemaVersion: "2" kind: mixin name: extra-egress permissions: network: allow: - registry.npmjs.org environment: variables: MY_MIXIN: "1" agentInstructions: content: | This mixin adds registry.npmjs.org to the sandbox's allowed egress. Global and per-sandbox policy still determine effective access. -
spec-sandbox.yaml 231 B
# Complete sandbox kit; save as spec.yaml in its own directory. # Inherits the embedded shell image and launch configuration. schemaVersion: "2" kind: sandbox name: my-shell extends: shell environment: variables: MY_KIT: "1"
-
-
checks
-
verification.md 6.5 KB
# Verification Runbook for kit spec.yaml commands `sbx kit` is EXPERIMENTAL. The local validation and inspection steps below do not create sandboxes; composing/creating does. This runbook does not publish artifacts or use a signing identity. This runbook is unexecuted; use an isolated, unique `--app-name` (≤20 characters) on every `sbx` invocation, a scratch registry namespace, and never a production signing key. Edits to a copied spec.yaml below use a small portable Python one-liner rather than `sed -i` (whose in-place syntax differs between BSD/macOS and GNU/Linux) so the runbook works on POSIX shells with Python 3. Docker login and a supported local runtime are prerequisites; login changes shared authentication, not just the test app. Run from the skill directory in one shell. `sbx kit validate` accepts a local **directory**, ZIP file, or git repository, not an OCI reference or a bare spec.yaml file. Other kit subcommands accept different reference types; consult their help. Copy each asset to a file named `spec.yaml` in its own directory before running these checks. ```bash APP="k-$(date +%s)-$$" # fresh suffix, at most 20 characters WORK=$(mktemp -d) mkdir "$WORK/kit-sandbox-example" "$WORK/kit-mixin-example" "$WORK/workspace" cp assets/spec-sandbox.yaml "$WORK/kit-sandbox-example/spec.yaml" cp assets/spec-mixin.yaml "$WORK/kit-mixin-example/spec.yaml" ``` ## 1. Validate both example kits (schema-only checks) ```bash sbx --app-name "$APP" kit validate "$WORK/kit-sandbox-example/" sbx --app-name "$APP" kit validate "$WORK/kit-mixin-example/" ``` Pass: both report valid with no errors. This proves only schema well-formedness — it does NOT compose either kit against a base agent and does NOT check any network reachability; see steps 3–4 for the difference. ## 2. Confirm strict decoding rejects an unknown field ```bash cp -r "$WORK/kit-mixin-example" "$WORK/kit-typo-mixin" python3 - "$WORK/kit-typo-mixin/spec.yaml" <<'PYCODE' import pathlib, sys p = pathlib.Path(sys.argv[1]) p.write_text(p.read_text().replace('permissions:', 'permisions:', 1)) PYCODE sbx --app-name "$APP" kit validate "$WORK/kit-typo-mixin/" ``` Pass: fails with an unrecognized-field error naming `permisions`, not a silent no-op. ## 3. Establish a truthful policy baseline before testing egress (isolated test app only) The genuine minimal mixin's `permissions.network.allow` (`registry.npmjs.org`) does not by itself prove anything is blocked: the default `balanced` global policy already allows many common hosts, and `allow-all` allows everything. To observe an actual denial, initialize a `deny-all` baseline — do this ONLY under this runbook's own isolated `--app-name`, never on a daemon you use for real work: ```bash sbx --app-name "$APP" policy init deny-all ``` Pass: `sbx --app-name "$APP" policy ls` shows the deny-all preset active for this isolated app only. ## 4. Confirm the all-egress-declared rule against the truthful baseline ```bash sbx --app-name "$APP" create --kit "$WORK/kit-mixin-example/" --name kit-egress-check shell "$WORK/workspace" sbx --app-name "$APP" policy check network --sandbox kit-egress-check registry.npmjs.org # allowed: this kit's own allow entry sbx --app-name "$APP" policy check network --sandbox kit-egress-check example.com # NOT allowed: deny-all baseline, no kit grants it ``` Pass: `registry.npmjs.org` is allowed (the mixin's own declared entry, on top of the deny-all baseline); an unrelated host (`example.com`) is not — this is what actually demonstrates the all-egress-declared/additive model, not merely removing an entry from one kit's `allow` list (which proves nothing about the effective policy on its own). ## 5. Confirm a mixin cannot declare a sandbox: block ```bash cp -r "$WORK/kit-mixin-example" "$WORK/kit-bad-mixin" printf '\nsandbox:\n image: docker/sandbox-templates:shell-docker\n' >> "$WORK/kit-bad-mixin/spec.yaml" sbx --app-name "$APP" kit validate "$WORK/kit-bad-mixin/" ``` Pass: fails — a `sandbox:` block is forbidden for `kind: mixin`. ## 6. Confirm a duplicate-service credential fails composition, not `kit validate` ```bash cp -r "$WORK/kit-mixin-example" "$WORK/kit-github-mixin" python3 - "$WORK/kit-github-mixin/spec.yaml" <<'PYCODE' import pathlib, sys p = pathlib.Path(sys.argv[1]) p.write_text(p.read_text() + ''' credentials: - service: github apiKey: name: GITHUB_TOKEN inject: - domain: api.github.com scheme: bearer ''') PYCODE sbx --app-name "$APP" kit validate "$WORK/kit-github-mixin/" # passes: schema-only sbx --app-name "$APP" create --kit "$WORK/kit-github-mixin/" --name kit-dup-check shell "$WORK/workspace" # expect: fails, shell already declares github ``` Pass: `kit validate` reports the kit as schema-valid on its own; only the actual `sbx create --kit` composition against `shell` (which already declares a `github` credential) fails with a duplicate-service error — confirming `kit validate` proves schema, not composability. ## 7. Confirm `sbx kit add` recreate-aware requirement ```bash sbx --app-name "$APP" run --name kit-add-check -d shell "$WORK/workspace" sbx --app-name "$APP" kit add kit-add-check "$WORK/kit-mixin-example/" sbx --app-name "$APP" kit inspect "$WORK/kit-mixin-example/" ``` Then check the actual sandbox, not just the artifact: ```bash sbx --app-name "$APP" exec kit-add-check printenv MY_MIXIN sbx --app-name "$APP" policy check network --sandbox kit-add-check registry.npmjs.org ``` Pass: `kit add` succeeds, the variable is `1`, and the host is allowed. `kit inspect` alone only shows the input artifact, not applied state. ## 8. Inspect the sandbox kit declaration without assuming inheritance was resolved ```bash sbx --app-name "$APP" kit inspect "$WORK/kit-sandbox-example/" --json | grep -E '"extends"[[:space:]]*:[[:space:]]*"shell"' ``` Pass: inspect reports `"extends": "shell"`. Local artifact inspection prints the declaration; it does not resolve the parent or print an inherited image. Parent resolution occurs during create/run. Source-level loader and composition checks confirm that this asset inherits the embedded shell image; this inspect command alone does not prove image availability. ## 9. Clean up (consented removal of this runbook's own isolated app and sandboxes) ```bash sbx --app-name "$APP" rm --force kit-add-check kit-egress-check sbx --app-name "$APP" daemon stop rm -rf "$WORK" ``` `--app-name "$APP"` isolates every command in this runbook to its own daemon; the `deny-all` baseline set in step 3 applies only to that isolated app and never touches the default daemon's policy.
-
-
references
-
kit-distribution-commands.md 4.7 KB
# Kit distribution and inspection commands: full reference Source-verified against docker/sandboxes commit `df5c96ba60484fa2c375469dbac912c205da6c37` and help captured from installed `sbx v0.42.0-503-g951b7f6d7` (commit `951b7f6d7f6bb260fac15077b607109ffe8ae012`); no source-only CLI-flag differences were found against the pinned-source `docs/yml/ sbx_kit_*.yaml` reference. `sbx kit` is EXPERIMENTAL. ## `sbx kit validate REFERENCE [flags]` Validates a local directory, ZIP, or git repository; OCI references are rejected. A kit with required arguments needs the same `--kit-arg` values `sbx create` would need, or it reports unresolved arguments rather than a false pass. **This checks the kit's own schema well-formedness only** — it never composes the kit against a base agent, so it cannot catch a duplicate-service credential collision (see SKILL.md's `kind: sandbox` vs `kind: mixin` section) or confirm any domain is actually reachable at runtime; both require actual composition via `sbx create --kit`/`sbx run --kit`, followed by a policy check. `kit inspect` never composes a mixin against a base. Flags: `--json`, `--kit-arg`, `--kit-args-file`. ## `sbx kit inspect REFERENCE [flags]` Loads and prints the decoded artifact **before** composing it into any sandbox: use it to confirm a kit's declared credentials, network rules, and setup commands look right, or to preview how a parameterized kit resolves with specific `--kit-arg` values (the output shows the substituted content). For a local kit with `extends:`, inspection prints the declared parent, not its resolved inherited image; create/run performs that resolution. ```bash sbx kit inspect ./my-mixin/ --kit-arg version=1.2.3 ``` Flags: `--json`, `--kit-arg`, `--kit-args-file`. ## `sbx kit pack DIRECTORY [flags]` Packages a validated directory (with its `spec.yaml` and optional `files/`) as a ZIP. Flags: `-o`/`--output` (default `<name>.zip`). ## `sbx kit pull REFERENCE [flags]` Pulls a kit artifact from an OCI registry and saves its raw layer payload to a file (`.zip` for `schemaVersion: "1"`, `.tar.gz` for `"2"`) without composing it into a sandbox — use it to inspect or archive a published kit's exact bytes. Authentication prefers an `sbx secret set --registry` credential, falling back to the Docker credential store. ```bash sbx kit pull ghcr.io/org/my-mixin:1.0 ``` Flags: `-o`/`--output`. ## `sbx kit push DIRECTORY REGISTRY/REPO:TAG [flags]` Packages and pushes; every push also attaches an **unsigned-by-default** SLSA provenance attestation naming the kit's content digests, declared image, and source git commit. Pass `--sign` for a Sigstore-signed manifest (keyless by default; `--key` for key-based). Flags: `--sign`, `--key`, `--identity-token`, `--identity-token-file`, `--tlog-upload` (default `true`). ## `sbx kit provenance REFERENCE [flags]` Prints the SLSA provenance attestation `sbx kit push` attached to an OCI kit. Printed as-is and marked **UNSIGNED** unless it was pushed with `--sign` and you pass matching `--key` (key-based) or `--certificate-identity`/`--certificate-oidc-issuer` (keyless) here — only then is it reported **VERIFIED**. Use this before trusting an unfamiliar published kit's declared source commit and image. ```bash sbx kit provenance ghcr.io/org/my-mixin:1.0 \ --certificate-identity user@example.com \ --certificate-oidc-issuer https://accounts.google.com ``` Flags: `--json`, `--key`, `--certificate-identity`, `--certificate-identity-regexp`, `--certificate-oidc-issuer`, `--certificate-oidc-issuer-regexp`, `--insecure-ignore-tlog`. ## `sbx kit sign REFERENCE [flags]` / `sbx kit verify REFERENCE [flags]` Sign or verify a local directory (writes/checks a `kit.sig.bundle` sidecar) or an OCI kit (attaches/checks a Sigstore bundle as an OCI referrer). Prefer `--identity-token-file` over `--identity-token` for keyless signing — the latter is visible in process args and shell history. Flags (sign): `--key`, `--identity-token`, `--identity-token-file`, `--tlog-upload`. Flags (verify): `--key`, `--certificate-identity`, `--certificate-identity-regexp`, `--certificate-oidc-issuer`, `--certificate-oidc-issuer-regexp`, `--insecure-ignore-tlog`, `--json`. ## `sbx kit add SANDBOX REFERENCE [flags]` Injects a **mixin** (only) into an **existing** sandbox at runtime, recreating its container while preserving kit-owned volumes and the workspace mount/clone state. The sandbox must have been created with the recreate-aware label set — an older sandbox is refused with a clear error, not silently degraded. Container-immutable settings (`security.privileged`, any `volumes:`) in the added kit cannot take effect on a running container — recreate the sandbox with the mixin at creation time instead. Flags: `--kit-arg`, `--kit-args-file`. -
sources.md 11 KB
# Sources ## Local pinned source (vendored spec package in repository docker/sandboxes, commit df5c96ba60484fa2c375469dbac912c205da6c37) Paths below are relative to the repository root. - `vendor/github.com/docker/sbx-kits-contrib/spec/types.go` — `Manifest`, `Security`, `MountSpec`/`MountType`, `Resources`, `BuildConfig`, `NetworkPolicy` (v1 legacy), `PublishedPort`, `Credential`/`ApiKey`/ `ApiKeyInject` (including `Scheme` sugar field and its doc comment), `Requires`, `KitArg`, `EnvironmentPolicy`, `CommandsPolicy`/ `InstallCommand`/`StartupCommand`/`InitFile`, `ArtifactFile`, `Artifact` (canonical model), `OAuth`/`OAuthTokenEndpoint`/`OAuthSentinels`/ `OAuthCredentialFile`, `SchemaVersion` constant ("1" default) and `SupportedSchemaVersions` (`["1","2"]`), `KindSandbox`/`KindAgent`/ `KindMixin` constants, `TargetHome`/`TargetWorkspace`. - `vendor/github.com/docker/sbx-kits-contrib/spec/v2.go` — `specFileV2` (clean v2 grammar), `sandboxBlockV2`, `agentInstructionsBlockV2`, `permissionsBlockV2`/`networkBlockV2`, `resourcesV2`, `setupBlockV2`, `commandFieldV2` (polymorphic decode), `toArtifact` (v2 -> canonical Artifact mapping, including the `sandbox.build` requires-image actionable-error path and the `mixins:` not-implemented warning), `expandCredentialSchemes` (the `scheme: bearer`/`basic` sugar expansion and its mutual-exclusivity-with-`format` check). - `vendor/github.com/docker/sbx-kits-contrib/spec/validate.go` — `ValidateManifest`/`validateManifest` (name pattern, template-required for sandbox unless `inheritsImage`), `ValidateArtifact` (full enforcement order: security, volumes, requires, mixin-must-not-extends-on-v2, locked, licenses, args, publishedPorts, environment, commands, credentials service-required, oauth structural checks, files target/path-escape checks) — note `ValidateArtifact` never checks cross-kit composition (duplicate services, effective network policy); it validates one artifact's own schema only. `ValidateRequires` (mixin-only, rejected on sandbox), `ValidateArgs`/`KitArg.ValidateValue` (enum XOR pattern, whole-value pattern anchoring), `ValidateOAuth` (sentinels required unless passthrough). - `vendor/github.com/docker/sbx-kits-contrib/spec/SPEC-v2.md` — normative v2 grammar reference: §2 common fields + `args`, §3 `kind: sandbox` (sandbox block, entrypoint/command effective-argv table, extends, mixins, complete example), §4 `kind: mixin` (field table, agent instructions progressive-disclosure model, `requires`, complete examples), §5 shared blocks (agentInstructions, permissions.network entry-format enforcement table and all-egress-declared rule, ports, credentials apiKey/oauth including scheme sugar table, environment reserved prefixes, setup command-shape table and user-model defaults, volumes and the ext4 sizing rationale, files/ directory rules), §6 validation summary, §7 composition & distribution (the immutable-pinning MUST is stated for `extends:`/`mixins:`/`--kit` remote references at the spec-document level; whether the CLI's own reference parser enforces it is a separate, implementation-level question — see `sandboxlib/kit/resolve.go` below), §9 runtime environment (user model, write surface, tool floor, architectures, injected env vars, lifecycle). - `sandboxlib/kitpolicy/kitpolicy.go` (NOT `sandboxlib/kit/kitpolicy/ kitpolicy.go` — corrected path) — `kit.allowedSources`, `kit.allowLocalKits`, `kit.requireSignature`, `kit.trustedSigners`, `kit.ignoreTransparencyLog`, `kit.allowExtractedAgents` settings that govern which kit sources/signatures a daemon accepts. - `sandboxlib/kit/resolve.go` — `ResolveReference` (the actual `--kit`/ `sbx kit *` reference parser): resolves a directory, ZIP, `oci://` prefix, bare `registry/repo:tag`, or `git+https://`/`git+ssh://` URL with an optional `#ref=...&dir=...` fragment. **No pinning is enforced by this parser** — a mutable OCI tag or an unpinned git ref/branch resolves the same as a digest- or SHA-pinned one; the "MUST be pinned" language lives only in SPEC-v2.md §7 for the `extends:`/`mixins:` fields inside `spec.yaml` itself. - `sandboxlib/kit/allowlist.go` — `AllowlistConfig.Check`/`Vouches` (the daemon-side allowlist gate on remote/local kit sources — a separate concern from spec-level pinning; it restricts WHICH remote hosts a kit may come from, not whether the reference is pinned). - `sandboxlib/kit/inject.go` — `InjectKit`/`injectArtifactContent` (environment/files/install/init-files/startup order for `sbx kit add`), warning printed for a kit whose `security.privileged`/`volumes` cannot apply to a running container ("Recreate the sandbox with this mixin at creation time to apply these settings"); `injectInitFiles`/ `buildInitFileShellCmd` confirm `commands.initFiles`/`setup.files` are written via shell exec — a mechanism distinct from the static `files/` tree's `injectFiles` (base64/atomic-rename write, not a shell command). - `sandboxlib/kit/kitargs.go` — `--kit-arg`/`kit.name=value` scoping syntax, `ErrKitArgUndeclared`/`ErrKitArgUnused` distinction (author bug vs. caller error). - `sandboxlib/agentkits/agents/claude/spec.yaml` — real-world v2 sandbox kit example: `permissions.network.allow` full list, block volumes with explicit `size:` and the same ext4-sizing rationale comment, `apiKey` (explicit `header`/`format`, no scheme sugar) + `oauth` (with `template:` credentialFile, not `structure:`) on the same `anthropic` credential, `setup.install`/`setup.startup` ordering and idempotency comments, `agentInstructions.filename: CLAUDE.md` + `content`. - `sandboxlib/agentkits/agents/shell/spec.yaml`, `sandboxlib/agentkits/agents/docker-agent/spec.yaml`, `sandboxlib/agentkits/agents/opencode/spec.yaml` — confirmed each declares its own `credentials: - service: github` entry, which is the concrete evidence for this skill's duplicate-service composition-failure rule: a mixin re-declaring `service: github` would collide with any of these three built-in base agents. ## Reviewer's throwaway schema harness (this session, no source modifications, no stateful sbx commands) - The reviewer built a `-tags filestore` schema-loading harness against the pinned source to check both original example kit YAMLs structurally. Both loaded and validated (schema-only), but composing the ORIGINAL `spec-mixin.yaml` (which declared its own `service: github` credential) against the `shell` base agent failed with a duplicate-service composition error — demonstrating exactly why `sbx kit validate`/schema loading is not sufficient evidence of composability, which is why this skill now states that distinction explicitly and the shipped mixin asset declares no credentials at all. ## Captured standalone CLI help, cross-checked against an older installed build Installed `sbx` reports `v0.42.0-503-g951b7f6d7` (commit `951b7f6d7f6bb260fac15077b607109ffe8ae012`), older than the pinned source HEAD. Verified with `sbx <cmd> --help` under an isolated `--app-name`, no daemon started. No source-only CLI-flag differences were found for the `sbx kit` commands this skill covers. - `sbx kit --help` — EXPERIMENTAL banner, subcommand list. - `sbx kit add --help` — recreate-aware label requirement, volume/workspace preservation across the swap container, `--kit-arg`/`--kit-args-file`. - `sbx kit inspect --help` — decoded-artifact preview, `--kit-arg` substitution preview. - `sbx kit pack --help` — directory-to-ZIP packaging, requires a valid `spec.yaml`. - `sbx kit validate --help` — directory/ZIP/git reference validation, required-argument `--kit-arg` requirement. - `sbx kit pull --help`/`sbx kit push --help` — schemaVersion-to-artifact- format mapping (`"1"` -> legacy ZIP, `"2"` -> OCI tar+gzip layer + manifest-config metadata), authentication precedence (sbx registry secrets over Docker credential store), provenance attachment on every push. - `sbx kit sign --help`/`sbx kit verify --help` — keyless (Fulcio+Rekor) vs. key-based signing, `--identity-token-file` preferred over `--identity-token`, `--tlog-upload=false` for private kits requiring a timestamp authority, `.sig.bundle` sidecar for local directories. - `sbx kit provenance --help` — SLSA provenance attachment as an OCI referrer, UNSIGNED-by-default reporting, keyless/key-based verification flags. ## Not verified / explicitly excluded - No claim is made that `mixins:` (author-time composition) is applied by the runtime this release — the vendored source (`v2.go`'s `toArtifact`, the `w.notImplemented("mixins", ...)` warning) and `SPEC-v2.md` §3.5 both state it is schema-accepted only; this skill states that explicitly rather than implying it works. - `SPEC-v2.md`'s network enforcement table is stale for CIDR and `**.` wildcards. The runtime implementation below takes precedence. Port ranges are not matched as ranges; exact ports are supported. - No docs.docker.com URL beyond the canonical product page (https://docs.docker.com/ai/sandboxes/) is cited here: this review did not independently fetch a dedicated kit-spec docs page, so no more specific URL is asserted as a source for any rule above. Every rule above traces to a repository path and/or a captured `--help` output. ## Runtime implementation cross-checks At docker/sandboxes commit `df5c96ba60484fa2c375469dbac912c205da6c37`: - `sandboxd/pkg/server/options_governance.go` — `ApplyKitNetworkPolicyScoped` passes kit entries to `local.NetworkRule` without filtering CIDR or globs. - `vendor/github.com/docker/governor-lib/internal/authorization/v2/rule_spec.go` — `lowerSpec` detects CIDR prefixes; other entries become domain rules. - `vendor/github.com/docker/governor-lib/internal/authorization/definitions/allowlist/v0/matching.go` — `MatchDomain` supports multi-label `**` globs; `matchCIDR` checks prefix containment; `portsEqual` compares exact ports, not ranges. - `sandboxd/pkg/proxy/engine_governance.go` — the `net:endpoint` request carries domain then resolved-IP identifiers. A decisive domain allow precedes a CIDR deny, as documented in `docs/yml/sbx_policy_deny.yaml`. - `sandboxlib/kit/resolve.go` — artifact references, not built-in agent names. - `sandboxlib/agentkits/resolver.go` and `sandboxlib/kitpolicy/kitpolicy.go` — built-in-only parent resolver; remote `extends` is not implemented. - `sandboxlib/kit/compose.go` — additive routing-only credentials, duplicate definitions, and rejection of mixin OAuth. - `sandboxlib/kit/signing/keys.go` — `loadPrivateKey`/`loadPublicKey` require ECDSA P-256 PEM keys; `readSecretFile` rejects private keys accessible to group/others. The local signing eval generates ephemeral keys outside the artifact with `umask 077`. - `sandboxlib/kit/signing/signing.go` — `signWithKey`/`verifyWithKey` use the supplied keys without Fulcio, Rekor, or an OIDC token; key-based verification checks the signed artifact against the bundle. - `AGENTS.md` — OpenAI OAuth precedence during provisioning; no universal API-key-first rule. -
spec-v2-fields.md 9.9 KB
# v2 kit spec.yaml field reference Schema-verified against the vendored spec package at docker/sandboxes commit df5c96ba60484fa2c375469dbac912c205da6c37 (`vendor/github.com/docker/sbx-kits-contrib/spec/types.go`, `vendor/github.com/docker/sbx-kits-contrib/spec/v2.go`) and `vendor/github.com/docker/sbx-kits-contrib/spec/SPEC-v2.md`. Kept here so the full field list does not have to be re-read from source on every lookup; SPEC-v2.md §6 ("Validation summary") is the authoritative enforcement list — consult it for exactly which of the rules below are checked by `ValidateArtifact` vs. enforced only by the engine at composition/runtime. ## Common top-level fields (both kinds) | Field | Required | Notes | |---|---|---| | `schemaVersion` | REQUIRED | Must be `"2"` for this grammar. | | `kind` | REQUIRED | `sandbox` or `mixin`. | | `name` | REQUIRED | `^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$`, unique across a composition. | | `version` | optional | Source for the OCI kit-version annotation. | | `displayName` | optional | Human-readable label. | | `description` | optional | Short description. | | `sourceURL` | optional | Source for the OCI `image.source` annotation. | | `licenses` | optional | SPDX identifiers, non-empty, no duplicates. | | `locked` | optional | Dotted paths child kits may not override; well-formedness only. | | `security.privileged` | optional | Immutable at runtime once set. | | `args` | optional | See below. | | `agentInstructions` | optional | Shared block; see below. | | `permissions.network` | optional | Shared block; see below. | | `ports` | optional | Shared block; see below. | | `credentials` | optional | Shared block; see below. | | `environment` | optional | Shared block; see below. | | `setup` | optional | Shared block; see below. | | `volumes` | optional | Shared block; see below. | | `files/` tree | optional | `files/home/` and `files/workspace/` only. | ## `kind: sandbox`-only fields | Field | Required | Notes | |---|---|---| | `sandbox` | **REQUIRED** (unless `extends` supplies it) | `image`/`build`, `entrypoint`, `command`, `resources`. | | `extends` | optional | Built-in agent name only at this pinned release; remote parents fail resolution. | | `mixins` | optional | Author-declared; forward-compat, not yet applied at runtime. | | `agentInstructions.filename` | optional | Meaningful only here — the AI profile this sandbox owns. | `sandbox.entrypoint`: flat string array, `entrypoint[0]` = binary. `command`: polymorphic — a bare list sets `default` (interactive falls back to it), or a `{default, interactive}` mapping. `sandbox.resources`: `cpu` (float, cores), `memory` (byte-size string, e.g. `4096m`/`8g`), `gpu` (opaque selector string). `sandbox.build` is forward-compat only (schema-accepted, not built by the runtime this release) and requires `image:` alongside it. ## `kind: mixin`-only fields | Field | Allowed | Notes | |---|---|---| | `sandbox` | **FORBIDDEN** | Hard error if present. | | `extends` | **FORBIDDEN** | Mixins cannot inherit. | | `mixins` | **FORBIDDEN** | Mixins cannot compose other mixins. | | `requires.agent` | optional | Base-agent affinity; rejected on `kind: sandbox`. | | `agentInstructions.filename` | ignored (warning) | A mixin does not own an AI profile filename. | | `volumes` | applies at create time only | `sbx kit add` skips volume changes. | ## `args` (v2 only) Map keyed by argument name (`^[A-Za-z_][A-Za-z0-9_-]*$`). Each entry: exactly one of `default` (string, `""` counts as real) or `required: true`; optional `description`; `enum` (list, no duplicates) XOR `pattern` (RE2, matched against the whole value). Referenced as `${{ kit.args.NAME }}`, substituted before decode; every reference must be declared or the kit fails to load. ## `agentInstructions` ```yaml agentInstructions: filename: CLAUDE.md # sandbox-only; ignored (warning) for a mixin content: | Markdown appended to (sandbox) or filed alongside (mixin) the AI profile. ``` ## `permissions.network` ```yaml permissions: network: allow: ["*.anthropic.com", "api.example.com:443"] deny: ["telemetry.example.com"] ``` Enforced: exact host, exact host+port, single-label wildcard (`*.example.com`), multi-label wildcard (`**.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, but a decisive domain decision precedes CIDR evaluation: a domain allow can bypass a CIDR deny for its resolved IP. `allow` lists are **additive across composition** — a sandbox's effective allow set is the union of every composed kit's `allow`, plus whatever the global/per-sandbox network policy independently permits (see `docker-sandboxes-network-credentials`). Removing a host from one kit's `allow` does NOT by itself prove that host is blocked. All-egress-declared: every `credentials[].apiKey.inject[].domain` should be declared in the kit's allow list for reproducibility. Omitting an entry does not necessarily block it: global/per-sandbox policy can grant access independently. `sbx kit validate` never checks reachability (schema-only). Confirm the real effective decision with `sbx policy check network --sandbox <name> <host>` against an actual sandbox. ## `ports` ```yaml ports: - container: 8080 # REQUIRED, 1-65535 protocol: tcp # "" (-> tcp) | tcp | udp name: web # informational only ``` Host ports are always ephemeral on `127.0.0.1`; a kit cannot pin one — users pin with `sbx ports --publish`. ## `credentials` ```yaml credentials: - service: anthropic # REQUIRED, identity for user-side bindings description: "..." required: false apiKey: name: ANTHROPIC_API_KEY # REQUIRED; env var set to sentinel when wired proxyManaged: true inject: - domain: api.anthropic.com # REQUIRED, must be allow-listed header: x-api-key # explicit header+format ... format: "%s" # ... exactly one %s - domain: api2.anthropic.com scheme: bearer # ... OR scheme sugar (mutually exclusive with format) oauth: tokenEndpoint: {host: platform.claude.com, path: /v1/oauth/token} # both REQUIRED resourceHosts: [api.anthropic.com] sentinels: {accessToken: "...", refreshToken: "..."} # REQUIRED unless passthrough credentialFile: {path: "~/.claude/.credentials.json", structure: {...}} # structure preferred over deprecated template passthrough: false # true = security downgrade, real token reaches container ``` `scheme: bearer` -> `header: Authorization, format: "Bearer %s"` (no `username`). `scheme: basic` -> username-driven Basic auth (`username` REQUIRED, no `header` set automatically). An entry may declare both `apiKey` and `oauth` on a sandbox kit. Do not infer universal credential precedence from this schema: provisioning is service-specific (stored usable OpenAI OAuth takes precedence over an API key). Mixins cannot declare OAuth. A mixin redefining a base service with its own `apiKey.name` or `proxyManaged` fails composition. Routing-only additions may merge: use only `apiKey.inject`, no `name`/`proxyManaged`/`oauth`, and `required: false`. Check the built-in base's source spec before authoring this extension; `sbx kit inspect shell` is not a supported lookup. For a custom base, inspect its directory/ZIP/OCI/git artifact reference instead. ## `environment` ```yaml environment: variables: IS_SANDBOX: "1" # keys must match ^[A-Za-z_][A-Za-z0-9_]*$ ``` Reserved prefixes the runtime owns (kits SHOULD NOT set): `DASH_`, `SBX_`, `DOCKER_`; runtime may also override `HOME`, `USER`, `SHELL`, `PATH`, `LD_PRELOAD`, `LD_LIBRARY_PATH`. ## `setup` ```yaml setup: install: # string command, sh -c, runs once - command: "install -d -o agent -g agent /home/agent/.cfg" user: "0" # default "0" (root) startup: # list<string> argv, runs every start - command: ["sh", "-c", "mkdir -p ~/.cfg"] user: "1000" # default "1000" (agent) background: false files: # dynamic writes performed at startup via shell exec - path: /home/agent/.cfg/config.json # REQUIRED, absolute content: '{"workdir": "${WORKDIR}"}' # only ${WORKDIR} placeholder allowed mode: "0644" onlyIfMissing: true ``` `install` runs once per kit at creation, for every kit (built-in or not) — guard with `command -v <bin>` for idempotency across recreate. `startup` must be idempotent (fires on every container start). `files` (under `setup:`) paths must be writable by uid 1000 — a root-owned target path needs an `install` command instead. All three lists concatenate across kits in `--kit` order. **`setup.files` is a different mechanism from the static `files/` directory tree (below).** `setup.files` entries are startup-time, `${WORKDIR}`- substituted dynamic writes; the `files/` directory tree is packed alongside `spec.yaml` and copied in at container-create time. It is specifically `files/workspace/<path>` (not `setup.files`) that is written after the workspace is populated (e.g. after an in-container `--clone` git clone) — do not attribute that "after workspace population" timing to `setup.files`. ## `volumes` ```yaml volumes: - path: /workspace # REQUIRED, absolute type: "" # "" (block, default) | tmpfs size: 10g # byte-size string mode: "0755" # octal ``` Creation-time only (`sbx kit add` skips volume changes). Always set `size:` on a block volume — an unsized one incurs ext4 inode-table zeroing at the 50 GiB default; 512 MiB is the practical floor. ## `files/` directory `files/home/<path>` -> `/home/agent/<path>`; `files/workspace/<path>` -> `<workspace>/<path>` (written after workspace population, e.g. after an in-container `--clone` git clone). Relative paths only; `..` traversal and symlinks escaping the artifact root are rejected.
-
-
SKILL.md 16.3 KB
--- name: docker-sandboxes-kits description: >- Use this skill when authoring, validating, packaging, signing, or composing a Docker Sandboxes kit `spec.yaml` (`sbx kit add/inspect/pack/pull/push/sign/validate/verify`), even if the user just says they want to "add a tool to a sandbox agent", "build a reusable sandbox extension", "publish a kit to a registry", or "give a mixin its own credentials and network access". Covers the kit-spec v2 grammar (`kind: sandbox` vs `kind: mixin`, the `sandbox:` block, `permissions.network`, `ports`, `credentials` apiKey/oauth, `environment`, `setup` install/startup/files, `volumes`, `args`, `extends`, `mixins`, `requires.agent`), composition via `--kit`/`sbx kit add`, and distribution (pack/push/pull/sign/verify/provenance). license: Apache-2.0 compatibility: Requires standalone sbx with sbx kit support and kit-spec schemaVersion "2", not the legacy docker sandbox wrapper. Verified against docker/sandboxes df5c96ba60484fa2c375469dbac912c205da6c37; installed-help version and provenance are in references/sources.md. docker_help does not cover standalone sbx. --- # Docker Sandboxes: Kits (spec.yaml) ## Overview 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`. ## When to use this skill Activate this skill when: - The user wants to write, validate, or pack a `spec.yaml` for a `kind: sandbox` (complete agent) or `kind: mixin` (extension) kit. - The user wants a mixin to add a tool, credential, network allowance, or files to an existing built-in agent. - The user wants to publish a kit to (or pull one from) an OCI registry, sign it, or verify a signature/provenance attestation. - The user is debugging a kit-validation error, an argument-substitution error, or `sbx kit add`'s recreate-aware requirement. ## Do not use this skill when Do not use this skill when: - The task is creating/running/removing the sandbox a kit is composed into, independent of the kit's own content — use `docker-sandboxes-lifecycle`. - The task is what a credential or network rule a kit declares actually 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`. - The task is the `sbxenv.yaml` file format that references kits via its own `kits:` block — use `docker-sandboxes-env` for that file's schema (this skill still owns what goes inside the referenced kit itself). ## Core guidance ### `kind: sandbox` vs `kind: mixin` — pick the right one - Exactly one `kind: sandbox` kit composes into any sandbox (a complete agent: 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:`. - Every kit needs `schemaVersion: "2"` (the current clean grammar — no legacy shims), `kind`, and `name` matching `^[a-z0-9]([a-z0-9-]{0,62}[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. ```yaml schemaVersion: "2" kind: mixin name: extra-egress ``` - Do not redefine a base agent's credential in a mixin: declaring a new `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. ### The `sandbox:` block (sandbox kits only) - **Required** for `kind: sandbox` (unless the kit `extends:` a parent that already supplies it); **forbidden** for `kind: mixin`. - `image:` is the pre-built base image. `entrypoint:` is the fixed process prefix (`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 the runtime 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 image without 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. ### Egress: `permissions.network` — and the all-egress-declared rule - `permissions.network.allow`/`deny` are the v2 home for what v1 spelled as top-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. ```yaml permissions: network: allow: - registry.npmjs.org deny: - telemetry.example.com ``` - **`permissions.network.allow` is additive across a composition, and a kit'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. - Declare the egress a kit requires explicitly for reproducibility. Credential 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 it - Each entry declares a `service` identity and **where to inject** the resolved 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 literal `proxy-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, unless `passthrough: 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`: ```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/` directory tree (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. - All three `setup:` lists **concatenate in `--kit` order** across composed kits. - Default execution users: install as root (`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 size - Each entry needs an absolute `path:`, optional `type: tmpfs` (RAM-backed; omit/`""` for the default block-backed volume), optional `size:` (byte-size string) and `mode:` (octal). - **Volumes apply only at sandbox-create time** — `sbx kit add` (runtime injection) skips volume changes entirely; a kit that needs one must be present at creation. - **Always set `size:` on a block volume.** An unsized volume inherits a 50 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 kit - Declare under top-level `args:` (v2 only — the frozen v1 grammar has no `args` 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. - Supply values with `--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 injection - `extends:` 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. - Prefer digest/commit-pinned CLI kit references for reproducibility. `--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 the single 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. ### Validating, packaging, and distributing | 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. ## Related skills - For the sandboxes a kit is composed into (`sbx create`/`run --kit`, `sbx kit add SANDBOX`), use `docker-sandboxes-lifecycle`. - For what a kit's `credentials:`/`permissions.network:` declarations mean at 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`. - For the `sbxenv.yaml` file whose `kits:`/`agent:` fields reference a kit by this schema, use `docker-sandboxes-env`. ## References - `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 - `assets/spec-sandbox.yaml` — a genuine minimal `kind: sandbox` kit that `extends: shell` to inherit a real, working image rather than inventing one. - `assets/spec-mixin.yaml` — a genuine minimal `kind: mixin` kit with no credentials at all (an egress-only extension), which composes cleanly with every built-in agent. ## Checks - `checks/verification.md` — Schema, composition, egress, and kit-add checks (unexecuted integration runbook; isolated `--app-name`, no registry publishing or signing). -
skill.yaml 1.2 KB
schema: v1 id: docker-sandboxes-kits version: 0.1.0 title: 'Docker Sandboxes: Kits (spec.yaml)' description: Author, validate, package, sign, and compose reusable sandbox/mixin kits (spec.yaml, schema v2). owns: - spec.yaml - sbx-kit - kit-spec-v2 use_when: - The user wants to write, validate, or pack a spec.yaml for a kind sandbox or kind mixin kit. - The user wants a mixin to add a tool, credential, network allowance, or files to an existing built-in agent. - The user wants to publish a kit to or pull one from an OCI registry, sign it, or verify a signature or provenance attestation. - The user is debugging a kit-validation error, an argument-substitution error, or sbx kit add's recreate-aware requirement. do_not_use_when: - The task is creating, running, or removing the sandbox a kit is composed into, independent of the kit's own content. - The task is what a credential or network rule a kit declares actually does at runtime, or secrets/policy unrelated to any kit. - The task is the sbxenv.yaml file format that references kits via its own kits block. delegates_to: - docker-sandboxes-lifecycle - docker-sandboxes-network-credentials - docker-sandboxes-env
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.