Claude Cursor Skill

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

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download docker-skills-skills_docker-sandboxes-kits-3e1cbd1.zip · 22 KB
docker/skills 436 23 forks Apache-2.0 Updated 11h ago
Part of docker/skills — 11 skills

Install

skills CLI npx skills add https://github.com/docker/skills/tree/main/skills/docker-sandboxes-kits
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install docker-skills@llmmart
Git 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.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.
    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.
    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, 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/ 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).
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.

No comments yet.

Reviews (0)

No reviews yet.

Related