From 9cc842d1e0981650b8b6df0903b2bad090daf9f5 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 5 May 2026 09:00:13 +0200 Subject: [PATCH 01/10] sandboxes/kits: surface --kit create-only behavior at first mention The mixin example uses `sbx run --kit` first. The note that --kit only applies on create lives further down, so users were trying to apply mixins to existing sandboxes and bouncing off the CLI error. Co-Authored-By: Claude Opus 4.7 (1M context) --- content/manuals/ai/sandboxes/customize/kits.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/content/manuals/ai/sandboxes/customize/kits.md b/content/manuals/ai/sandboxes/customize/kits.md index 476ee8366ef..d89973d9c70 100644 --- a/content/manuals/ai/sandboxes/customize/kits.md +++ b/content/manuals/ai/sandboxes/customize/kits.md @@ -209,12 +209,16 @@ select = ["E", "F", "I"] > The templates for the built-in agents (`claude`, `codex`, etc) already > includes `uv`, so this mixin can use it without installing it separately. -To run an agent with this mixin: +To start a new sandbox with this mixin: ```console $ sbx run claude --kit /path/to/ruff-lint/ ``` +To apply the mixin to a sandbox that's already running, use +[`sbx kit add`](#local) instead. The `--kit` flag only takes effect when a +sandbox is created. + ## Agent kits An agent kit defines a full agent from scratch — image, entrypoint, and From 216c87be30c6c866fb7e6a5a8bcd4cce8256dc0e Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 5 May 2026 09:00:42 +0200 Subject: [PATCH 02/10] sandboxes: document private-image limitation to Docker Hub The image-pull credential resolver authenticates with the user's sbx login session for Docker Hub references and falls back to anonymous for everything else. So private templates and kits on ghcr.io, ECR, Nexus, and similar fail to pull silently. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../manuals/ai/sandboxes/customize/kits.md | 6 ++++++ .../ai/sandboxes/customize/templates.md | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/content/manuals/ai/sandboxes/customize/kits.md b/content/manuals/ai/sandboxes/customize/kits.md index d89973d9c70..23ad155c2b1 100644 --- a/content/manuals/ai/sandboxes/customize/kits.md +++ b/content/manuals/ai/sandboxes/customize/kits.md @@ -332,6 +332,12 @@ $ sbx run claude --kit ghcr.io/myorg/my-kit:1.0 For Docker Hub, include the full `docker.io` prefix. See [Packaging and distribution](#packaging-and-distribution) for publishing. +> [!IMPORTANT] +> Private kits are only supported on Docker Hub. `sbx` reuses your +> `sbx login` session to pull private artifacts from Docker Hub. Other +> registries are pulled anonymously, so private kits hosted on +> registries other than Docker Hub fail to pull. + ## Packaging and distribution The `sbx kit` subcommands validate, inspect, and publish kits: diff --git a/content/manuals/ai/sandboxes/customize/templates.md b/content/manuals/ai/sandboxes/customize/templates.md index 9340964a772..25367ae0e0d 100644 --- a/content/manuals/ai/sandboxes/customize/templates.md +++ b/content/manuals/ai/sandboxes/customize/templates.md @@ -117,6 +117,27 @@ $ docker build -t my-org/my-template:v1 --push . > registry directly; it doesn't share the image store of your local Docker > daemon on the host. +> [!IMPORTANT] +> Private templates are only supported on Docker Hub. `sbx` reuses your +> `sbx login` session to pull private images from Docker Hub. Other +> registries (such as GitHub Container Registry, ECR, or a self-hosted +> registry like Nexus) are pulled anonymously, so private images on those +> registries fail to pull. + +For locally-built images or private images on registries that `sbx` +can't authenticate against, save the image to a tar and load it +directly into the sandbox runtime instead of pulling from a registry: + +```console +$ docker image save my-org/my-template:v1 -o my-template.tar +$ sbx template load my-template.tar +$ sbx run --template my-org/my-template:v1 claude +``` + +`sbx template load` imports the tar into the sandbox runtime's image +store, so the image doesn't need to be reachable from a registry at +sandbox creation time. + Unless you use the permissive `allow-all` network policy, you may also need to allow-list any domains that your custom tools depend on: From d205d303bd65310e5ec7219514112181f42dafbf Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 5 May 2026 09:00:59 +0200 Subject: [PATCH 03/10] sandboxes/credentials: drop AWS_SECRET_ACCESS_KEY from supported services The aws credential source in the sandbox kit specs only reads AWS_ACCESS_KEY_ID; AWS_SECRET_ACCESS_KEY is not recognized today. Listing it in the table misled users who tried to authenticate Bedrock with key/secret pairs or STS tokens and silently got only the access key id. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ai/sandboxes/security/credentials.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/content/manuals/ai/sandboxes/security/credentials.md b/content/manuals/ai/sandboxes/security/credentials.md index e038d0df048..4823e121382 100644 --- a/content/manuals/ai/sandboxes/security/credentials.md +++ b/content/manuals/ai/sandboxes/security/credentials.md @@ -66,17 +66,17 @@ $ echo "$ANTHROPIC_API_KEY" | sbx secret set -g anthropic Each service name maps to a set of environment variables the proxy checks and the API domains it authenticates requests to: -| Service | Environment variables | API domains | -| ----------- | -------------------------------------------- | ----------------------------------- | -| `anthropic` | `ANTHROPIC_API_KEY` | `api.anthropic.com` | -| `aws` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` | AWS Bedrock endpoints | -| `github` | `GH_TOKEN`, `GITHUB_TOKEN` | `api.github.com`, `github.com` | -| `google` | `GEMINI_API_KEY`, `GOOGLE_API_KEY` | `generativelanguage.googleapis.com` | -| `groq` | `GROQ_API_KEY` | `api.groq.com` | -| `mistral` | `MISTRAL_API_KEY` | `api.mistral.ai` | -| `nebius` | `NEBIUS_API_KEY` | `api.studio.nebius.ai` | -| `openai` | `OPENAI_API_KEY` | `api.openai.com` | -| `xai` | `XAI_API_KEY` | `api.x.ai` | +| Service | Environment variables | API domains | +| ----------- | ---------------------------------- | ----------------------------------- | +| `anthropic` | `ANTHROPIC_API_KEY` | `api.anthropic.com` | +| `aws` | `AWS_ACCESS_KEY_ID` | AWS Bedrock endpoints | +| `github` | `GH_TOKEN`, `GITHUB_TOKEN` | `api.github.com`, `github.com` | +| `google` | `GEMINI_API_KEY`, `GOOGLE_API_KEY` | `generativelanguage.googleapis.com` | +| `groq` | `GROQ_API_KEY` | `api.groq.com` | +| `mistral` | `MISTRAL_API_KEY` | `api.mistral.ai` | +| `nebius` | `NEBIUS_API_KEY` | `api.studio.nebius.ai` | +| `openai` | `OPENAI_API_KEY` | `api.openai.com` | +| `xai` | `XAI_API_KEY` | `api.x.ai` | When you store a secret with `sbx secret set -g `, the proxy uses it the same way it would use the corresponding environment variable. You don't From ff2734ea51001426f23373f36d54276bdb6f850a Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 5 May 2026 09:37:08 +0200 Subject: [PATCH 04/10] Revert "docs: remove internal sandbox kit links" This reverts commit 8a652d0f156ba8debc5af3677f692bcde6860ab6. --- .../ai/sandboxes/customize/build-an-agent.md | 11 +++++++++++ .../ai/sandboxes/customize/kit-examples.md | 17 +++++++++++++++++ content/manuals/ai/sandboxes/customize/kits.md | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/content/manuals/ai/sandboxes/customize/build-an-agent.md b/content/manuals/ai/sandboxes/customize/build-an-agent.md index b8560d89221..2e045830c2b 100644 --- a/content/manuals/ai/sandboxes/customize/build-an-agent.md +++ b/content/manuals/ai/sandboxes/customize/build-an-agent.md @@ -21,6 +21,10 @@ behind a part of the spec, so you can apply the same reasoning to other agents. For reference on every field, see the [Kits](kits.md) page. This tutorial focuses on the journey. +The finished kit is also published as a runnable sample at +[docker/sbx-kits-contrib](https://github.com/docker/sbx-kits-contrib/tree/main/amp) — +useful as a reference while you follow along. + ## Choose a base image An agent kit needs a container image that satisfies the @@ -261,6 +265,13 @@ agent argument: $ sbx run --kit ./amp/ amp ``` +The published copy of this kit also runs directly from the contrib +repo: + +```console +$ sbx run --kit "git+https://github.com/docker/sbx-kits-contrib.git#dir=amp" amp +``` + ## Iterate As you use the kit, you'll likely hit missing domains or install quirks. diff --git a/content/manuals/ai/sandboxes/customize/kit-examples.md b/content/manuals/ai/sandboxes/customize/kit-examples.md index 2974fb0f959..411298c5e2a 100644 --- a/content/manuals/ai/sandboxes/customize/kit-examples.md +++ b/content/manuals/ai/sandboxes/customize/kit-examples.md @@ -129,6 +129,12 @@ command can invoke it directly. Use `initFiles` instead of a static file whenever the content depends on a runtime value. Use a static file otherwise. +> [!TIP] +> This snippet is lifted from the +> [code-server kit](https://github.com/docker/sbx-kits-contrib/tree/main/code-server) +> in the contrib repo, which is also a runnable sample that demonstrates +> the full pattern. + ## Ship a Claude Code skill Claude Code reads project-scoped skills from @@ -223,3 +229,14 @@ $ sbx run claude-safe --kit ./claude-safe For a step-by-step walkthrough of building a new agent kit from scratch, see [Build an agent](build-an-agent.md). + +## More examples + +These patterns are all drawn from working kits in the +[sbx-kits-contrib](https://github.com/docker/sbx-kits-contrib) +repository, which contains each example as a complete, loadable kit. +Use it to study the full shape of a kit, or load one directly: + +```console +$ sbx run claude --kit "git+https://github.com/docker/sbx-kits-contrib.git#dir=" +``` diff --git a/content/manuals/ai/sandboxes/customize/kits.md b/content/manuals/ai/sandboxes/customize/kits.md index 23ad155c2b1..e44da0f62e0 100644 --- a/content/manuals/ai/sandboxes/customize/kits.md +++ b/content/manuals/ai/sandboxes/customize/kits.md @@ -313,7 +313,7 @@ removed from a running sandbox — remove and recreate it to start clean. ### Git repository ```console -$ sbx run claude --kit "git+https://github.com//.git#ref=v0.1.0&dir=code-server" +$ sbx run claude --kit "git+https://github.com/docker/sbx-kits-contrib.git#ref=v0.1.0&dir=code-server" ``` - `#ref=` pins to a specific revision. Defaults to the From 16d4b7a27df83f34b29a420560593d710e7be05d Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 5 May 2026 09:53:40 +0200 Subject: [PATCH 05/10] sandboxes/faq: show how to default to approval prompts via a kit Per-session /permissions only works for Claude Code and resets every session. Document the agent-kit override pattern so users who want approval prompts as the default have a recipe that survives sandbox recreation and works regardless of agent. Closes docker/sbx-releases#47. Co-Authored-By: Claude Opus 4.7 (1M context) --- content/manuals/ai/sandboxes/faq.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/content/manuals/ai/sandboxes/faq.md b/content/manuals/ai/sandboxes/faq.md index d4ccef99e19..6a1b0919acb 100644 --- a/content/manuals/ai/sandboxes/faq.md +++ b/content/manuals/ai/sandboxes/faq.md @@ -98,6 +98,24 @@ inside the session. Most agents let you switch permission modes after startup. In Claude Code, use the `/permissions` command to change the mode interactively. +To make approval prompts the default for every session, define a custom +agent kit that overrides the agent's entrypoint to drop the +permission-skipping flag. For example, a kit that launches Claude Code +without `--dangerously-skip-permissions`: + +```yaml {title="claude-safe/spec.yaml"} +schemaVersion: "1" +kind: agent +name: claude-safe +agent: + image: "docker/sandbox-templates:claude-code-docker" + entrypoint: + run: [claude] +``` + +Run it with `sbx run claude-safe --kit ./claude-safe/`. See +[Agent kits](customize/kits.md#agent-kits) for the full pattern. + ## How do I know if my agent is running in a sandbox? Ask the agent. The agent can see whether or not it's running inside a sandbox. From 52961cf900cd175530b9517a3aa5370f49229030 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 5 May 2026 09:54:43 +0200 Subject: [PATCH 06/10] sandboxes/customize: explain templates and kits side by side First-time users repeatedly bounce off one question: what's the relationship between templates and kits, and when do I use which? The existing "When to use which" table on the customize landing page gives goal-to-option mapping but doesn't define each, name the connection point (a kit's agent.image is a template), or say that they compose. Add a "Templates and kits, side by side" section that does. Refs docker/sbx-releases#103 (the templates-vs-kits part). The same issue calls out confusion about how kit credentials wire to sbx secret set vs host env vars vs sbx secret set-custom. That's a broader rewrite of the kits credentials section and is being deferred to a separate change. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../manuals/ai/sandboxes/customize/_index.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/content/manuals/ai/sandboxes/customize/_index.md b/content/manuals/ai/sandboxes/customize/_index.md index 6b3de68986c..fbace2b306c 100644 --- a/content/manuals/ai/sandboxes/customize/_index.md +++ b/content/manuals/ai/sandboxes/customize/_index.md @@ -25,6 +25,27 @@ creating, loading, and managing kits are subject to change as the feature evolves. Share feedback and bug reports in the [docker/sbx-releases](https://github.com/docker/sbx-releases) repository. +## Templates and kits, side by side + +A **template** is a Docker image that the sandbox runs. It's built ahead +of time with a Dockerfile (or saved from a running sandbox), pushed to a +registry, and pulled when a sandbox is created. Use templates for things +that belong in an image: system packages, language toolchains, large +dependencies — anything you'd rather not reinstall on every sandbox start. + +A **kit** is a YAML artifact applied at sandbox creation. The kit can run +install commands, drop files into the sandbox, declare network and +credential rules, and (for agent kits) define which template image the +agent runs in. Use kits for things that vary per agent or per team: +shared linter config, project-specific install steps, credential +injection for a service the agent talks to. + +The two compose. An agent kit's `agent.image` field points at a template: +the template provides the base environment, the kit layers config, +secrets, and runtime behavior on top. A team can ship one heavy template +and several thin kits without rebuilding the image each time something +changes. + ## When to use which | Goal | Option | From 652ba045cf3c1cca5df07c135a71c3d6dc039109 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 5 May 2026 10:29:08 +0200 Subject: [PATCH 07/10] sandboxes: rewrite kit credentials section as one chain, document set-custom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kits page split authenticated-service wiring across two adjacent subsections — "Control network access" carried the serviceDomains/serviceAuth example, and "Declare credential sources" carried the credentials.sources example — but the two only make sense together. New kit authors couldn't tell which block tied which (the service identifier is the join key) or where the value actually comes from. The wider question — does sbx secret set work for custom kit services? — has no answer in the docs today. Restructure: - Strip "Control network access" down to allowedDomains, the only block that's independent of credentials. Cross-link to the new authentication section. - Replace "Declare credential sources" with a new "Authenticate to external services" section that shows the full three-block chain in one example, walks through what the proxy does at request time, and spells out resolution order: stored secret keyed on the service identifier first, host env var fallback. Confirmed end-to-end against sbx v0.28.3 with a custom kit (kit-named keychain entries do flow through to the proxy). - Add a "Custom secrets" section to security/credentials.md documenting sbx secret set-custom with the EXPERIMENTAL caveat, so the kits page has a real cross-reference target. The build-an-agent tutorial already uses set-custom but the credentials page didn't acknowledge it existed. Closes docker/sbx-releases#103. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../manuals/ai/sandboxes/customize/kits.md | 64 +++++++++++++++---- .../ai/sandboxes/security/credentials.md | 35 ++++++++++ 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/content/manuals/ai/sandboxes/customize/kits.md b/content/manuals/ai/sandboxes/customize/kits.md index e44da0f62e0..0eb6877c929 100644 --- a/content/manuals/ai/sandboxes/customize/kits.md +++ b/content/manuals/ai/sandboxes/customize/kits.md @@ -112,36 +112,72 @@ environment: ### Control network access -Network rules define which domains the sandbox can reach. For -authenticated services, a domain can be mapped to a service identifier, -and the proxy injects the auth header on forwarded requests: +Network rules define which domains the sandbox can reach: ```yaml network: allowedDomains: - api.example.com - serviceDomains: - api.example.com: my-service - serviceAuth: - my-service: - headerName: Authorization - valueFormat: "Bearer %s" + - "*.cdn.example.com" ``` -### Declare credential sources +For authenticated services, see +[Authenticate to external services](#authenticate-to-external-services). -Credential sources tell the proxy where to find secrets on the host. The -sandbox never sees the value itself. The proxy reads it and injects it -into outbound requests: +### Authenticate to external services + +A kit can attach credentials to outbound requests without exposing the +secret to the agent. The host-side proxy reads the credential and +injects it as a header when a request leaves the sandbox; the agent +inside the VM never sees the value. + +The wiring has three blocks, tied together by a service identifier +that you choose (here, `my-service`): ```yaml +network: + allowedDomains: + - api.example.com + serviceDomains: + api.example.com: my-service # Tag traffic to this domain + serviceAuth: + my-service: + headerName: Authorization # Attach the credential as this header + valueFormat: "Bearer %s" # ...formatted like this + credentials: sources: my-service: env: - - MY_SERVICE_API_KEY + - MY_SERVICE_API_KEY # Where to read the credential on the host +``` + +When the sandbox sends a request to `api.example.com`, the proxy tags +it `my-service`, looks up the credential, and sets +`Authorization: Bearer ` on the request before forwarding. + +The credential value is resolved on the host, in this order: + +1. A stored secret keyed on the service identifier + (`sbx secret set -g my-service`). +2. Any host environment variable listed under + `credentials.sources..env`. + +Storing the value in your OS keychain avoids exporting it in every +shell: + +```console +$ sbx secret set -g my-service ``` +The keychain entry is keyed on the same service identifier the kit +declares — there's no separate service registration step. + +For credentials that don't fit this shape — for example, a value used +in a request body, or a header that mixes the secret with other text — +see [Custom secrets](../security/credentials.md#custom-secrets) for an +experimental placeholder-substitution alternative. + ### Define an agent Agent kits declare an `agent:` block with the image the agent runs in and diff --git a/content/manuals/ai/sandboxes/security/credentials.md b/content/manuals/ai/sandboxes/security/credentials.md index 4823e121382..bfcabbe8927 100644 --- a/content/manuals/ai/sandboxes/security/credentials.md +++ b/content/manuals/ai/sandboxes/security/credentials.md @@ -128,6 +128,41 @@ commit signing to work. Outbound SSH connections are still subject to sandbox network policy. For details, see [Signed commits](../usage.md#signed-commits). +## Custom secrets + +> [!IMPORTANT] +> Custom secrets are experimental. The `set-custom` subcommand is +> hidden from `sbx --help`, and behavior, flags, and the placeholder +> format may change. + +For services that aren't in the [supported services](#supported-services) +table and don't fit the service-identifier model used by +[kits](../customize/kits.md#authenticate-to-external-services), +`sbx secret set-custom` stores a secret keyed on a target host and +environment variable name. The sandbox sees the env var set to a +generated placeholder; the proxy substitutes the real secret into +outbound traffic to the target host before forwarding. + +```console +$ sbx secret set-custom -g \ + --host api.example.com \ + --env API_KEY \ + --value +``` + +Inside the sandbox, `API_KEY` contains a placeholder string (for +example, `sbx-cs-`). When a sandboxed process sends a request +to `api.example.com` and the placeholder appears anywhere in the +request, the proxy replaces it with the real value. The agent never +sees the real secret. + +Use `set-custom` only when the service-based flow doesn't fit — for +example, when the credential lands in a request body rather than a +header, when the value is mixed with other text in a header, or +when you don't want to author a kit. Prefer the +[stored secret](#stored-secrets) flow whenever it's an option; it's +simpler and the credential shape is constrained. + ## Environment variables As an alternative to stored secrets, export the relevant environment variable From 830edad3dca2a0f58c711c395fc5cd542106b3b1 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 5 May 2026 10:34:58 +0200 Subject: [PATCH 08/10] sandboxes: refactor kit credentials and credentials page for coherence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Authenticate to external services" section in customize/kits.md had grown into a deep treatise inside what's otherwise a tour of kit capabilities. Most of the conceptual model belongs on the credentials page, not in a capability tour. The credentials page in turn was implicitly framed around built-in agents only — kit-declared service identifiers (which work end-to-end with sbx secret set) weren't acknowledged anywhere. Restructure: - customize/kits.md: cut "Authenticate to external services" to a show-then-link entry. Four-block YAML with brief inline comments, one paragraph on runtime behavior, two cross-links to the credentials page. The conceptual depth lives there now. - security/credentials.md: * Add a "How credential injection works" section after the intro that names the three host-side mechanisms (service-based, custom by host+env, host shell env) and links to each. * Rename the "Supported services" subsection to "Built-in services" and add a sibling "Services declared by kits" subsection documenting the kit-declared identifier path for the first time. * Tighten the "Custom secrets" framing — it's no longer positioned as the catch-all for things kits can't do; it's specifically for cases where the service-identifier model doesn't fit (format-validating SDKs, request-body secrets). - Update the FAQ cross-link to credentials#built-in-services. Net effect: each page does one job. Kit authors get a tour entry that points to the canonical model; credential consumers get a single page that covers all three mechanisms with the missing kit-declared services pattern documented. Closes docker/sbx-releases#103. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../manuals/ai/sandboxes/customize/kits.md | 62 ++++----- content/manuals/ai/sandboxes/faq.md | 2 +- .../ai/sandboxes/security/credentials.md | 124 +++++++++++------- 3 files changed, 103 insertions(+), 85 deletions(-) diff --git a/content/manuals/ai/sandboxes/customize/kits.md b/content/manuals/ai/sandboxes/customize/kits.md index 0eb6877c929..b37997d8ec5 100644 --- a/content/manuals/ai/sandboxes/customize/kits.md +++ b/content/manuals/ai/sandboxes/customize/kits.md @@ -98,18 +98,19 @@ See [`initFiles`](#initfiles) in the spec reference for all fields. ### Set environment variables Environment variables set by the kit are available to the agent at -runtime. Sensitive values can be marked proxy-managed, so the real -credential is substituted only when the proxy forwards a request. The -secret itself never enters the VM: +runtime: ```yaml environment: variables: MY_TOOL_WORKSPACE: /home/agent/my-tool - proxyManaged: - - MY_TOOL_API_KEY ``` +For credentials, see +[Authenticate to external services](#authenticate-to-external-services). +Don't put secret values directly in `environment.variables` — they'd +be visible inside the sandbox VM. + ### Control network access Network rules define which domains the sandbox can reach: @@ -126,13 +127,13 @@ For authenticated services, see ### Authenticate to external services -A kit can attach credentials to outbound requests without exposing the -secret to the agent. The host-side proxy reads the credential and -injects it as a header when a request leaves the sandbox; the agent -inside the VM never sees the value. +A kit can attach credentials to outbound requests through the +host-side proxy. The agent inside the VM works with a sentinel value; +the proxy reads the real credential on the host and overwrites the +auth header before the request leaves the sandbox. -The wiring has three blocks, tied together by a service identifier -that you choose (here, `my-service`): +The standard pattern uses four blocks tied to a service identifier +you choose (here, `my-service`): ```yaml network: @@ -142,41 +143,28 @@ network: api.example.com: my-service # Tag traffic to this domain serviceAuth: my-service: - headerName: Authorization # Attach the credential as this header - valueFormat: "Bearer %s" # ...formatted like this + headerName: Authorization # Overwrite this header + valueFormat: "Bearer %s" credentials: sources: my-service: env: - - MY_SERVICE_API_KEY # Where to read the credential on the host -``` - -When the sandbox sends a request to `api.example.com`, the proxy tags -it `my-service`, looks up the credential, and sets -`Authorization: Bearer ` on the request before forwarding. - -The credential value is resolved on the host, in this order: + - MY_SERVICE_API_KEY # Host-side credential lookup -1. A stored secret keyed on the service identifier - (`sbx secret set -g my-service`). -2. Any host environment variable listed under - `credentials.sources..env`. - -Storing the value in your OS keychain avoids exporting it in every -shell: - -```console -$ sbx secret set -g my-service +environment: + proxyManaged: + - MY_SERVICE_API_KEY # Set the in-VM env var to "proxy-managed" ``` -The keychain entry is keyed on the same service identifier the kit -declares — there's no separate service registration step. +The agent boots with `MY_SERVICE_API_KEY=proxy-managed`, sends a +request with that value in `Authorization`, and the proxy overwrites +the header with the real credential before forwarding. The real +secret never enters the VM. -For credentials that don't fit this shape — for example, a value used -in a request body, or a header that mixes the secret with other text — -see [Custom secrets](../security/credentials.md#custom-secrets) for an -experimental placeholder-substitution alternative. +See [Credentials](../security/credentials.md) for how to provide the +credential value on your host, other approaches for cases the example +above doesn't fit, and what the proxy does at request time. ### Define an agent diff --git a/content/manuals/ai/sandboxes/faq.md b/content/manuals/ai/sandboxes/faq.md index 6a1b0919acb..61597619708 100644 --- a/content/manuals/ai/sandboxes/faq.md +++ b/content/manuals/ai/sandboxes/faq.md @@ -44,7 +44,7 @@ $ export SBX_NO_TELEMETRY=1 ## How do I set custom environment variables inside a sandbox? The [`sbx secret`](/reference/cli/sbx/secret/) command only supports a fixed set -of [services](security/credentials.md#supported-services) (Anthropic, OpenAI, +of [services](security/credentials.md#built-in-services) (Anthropic, OpenAI, GitHub, and others). If your agent needs an environment variable that isn't tied to a supported service, such as `BRAVE_API_KEY` or a custom internal token, write it to `/etc/sandbox-persistent.sh` inside the sandbox. This diff --git a/content/manuals/ai/sandboxes/security/credentials.md b/content/manuals/ai/sandboxes/security/credentials.md index bfcabbe8927..d94b420df6f 100644 --- a/content/manuals/ai/sandboxes/security/credentials.md +++ b/content/manuals/ai/sandboxes/security/credentials.md @@ -8,32 +8,53 @@ keywords: docker sandboxes, credentials, api keys, authentication, proxy, ssh ag {{< summary-bar feature_name="Docker Sandboxes sbx" >}} Most agents need an API key for their model provider. An HTTP/HTTPS proxy on -your host intercepts outbound API requests from the sandbox and injects the -appropriate authentication headers before forwarding each request. Your -credentials stay on the host and are never stored inside the sandbox VM. For -how this works as a security layer, see +your host intercepts outbound requests from the sandbox, looks up the matching +credential on the host, and overwrites the auth header before forwarding. The +real credential stays on the host; the sandbox sees only a sentinel value. For +the security model behind this, see [Credential isolation](isolation.md#credential-isolation). -There are two ways to provide credentials: - -- **Stored secrets** (recommended): saved in your OS keychain, encrypted and - persistent across sessions. -- **Environment variables:** read from your current shell session. This works - but is less secure on the host side, since environment variables are visible - to other processes running as your user. - -If both are set for the same service, the stored secret takes precedence. For -multi-provider agents (OpenCode, Docker Agent), the proxy automatically selects the -correct credentials based on the API endpoint being called. See individual -[agent pages](../agents/) for provider-specific details. +## How credential injection works + +The proxy needs three things to inject a credential: which outbound traffic to +match, what header to write, and what value to use. The kit (or built-in agent +definition) declares the first two. You provide the value on the host. + +There are two host-side stores, plus a host shell fallback: + +- **Stored secrets, by service identifier.** Built-in agents declare service + identifiers (`anthropic`, `openai`, `github`, etc.) in their kit specs; + custom kits can declare their own. `sbx secret set` stores a value keyed on + that identifier. When a sandboxed request matches a service's domain, the + proxy reads the stored value and writes the configured header. Inside the + sandbox, the env var holds a sentinel like `proxy-managed`, so SDKs that + read the variable see something non-empty without seeing the real secret. + See [Stored secrets](#stored-secrets). + +- **Stored secrets, by target domain and env var name.** `sbx secret set-custom` + stores a value alongside a target domain, an env var name, and an optional + placeholder. The sandbox sees the placeholder; the proxy substitutes it + with the real value anywhere it appears in outbound traffic to that + domain. Use this when the service-identifier model doesn't fit — for + example, when the agent validates the env var format at boot, or when the + credential lands in a request body. See [Custom secrets](#custom-secrets). + +- **Host shell environment variables.** As a fallback, the proxy reads from + your shell environment. Useful for one-off testing or development; stored + secrets are preferred because shell environment variables are plaintext + and visible to other processes running as your user. See + [Environment variables](#environment-variables). + +If both a stored secret and a host env var are set for the same service, the +stored secret takes precedence. For multi-provider agents (OpenCode, Docker +Agent), the proxy selects credentials based on the API endpoint being called. +See individual [agent pages](../agents/) for provider-specific details. ## Stored secrets -The `sbx secret` command stores credentials in your OS keychain so you don't -need to export environment variables in every terminal session. When a sandbox -starts, the proxy looks up stored secrets and uses them to authenticate API -requests on behalf of the agent. The secret is never exposed directly to the -agent. +`sbx secret set` stores credentials in your OS keychain, keyed on a service +identifier. Built-in agents declare a fixed set of services. Custom kits can +declare their own. The same `sbx secret set` flow works for both. ### Store a secret @@ -61,10 +82,10 @@ You can also pipe in a value for non-interactive use: $ echo "$ANTHROPIC_API_KEY" | sbx secret set -g anthropic ``` -### Supported services +### Built-in services -Each service name maps to a set of environment variables the proxy checks and -the API domains it authenticates requests to: +Each built-in service name maps to a set of environment variables the proxy +checks and the API domains it authenticates requests to: | Service | Environment variables | API domains | | ----------- | ---------------------------------- | ----------------------------------- | @@ -82,6 +103,22 @@ When you store a secret with `sbx secret set -g `, the proxy uses it the same way it would use the corresponding environment variable. You don't need to set both. +### Services declared by kits + +Custom kits can declare their own service identifiers in `spec.yaml` — +they're not limited to the table above. To provide a credential for a +kit-declared service, run `sbx secret set` with the same identifier the kit +declares under `credentials.sources`: + +```console +$ sbx secret set -g my-service +``` + +There's no separate registration step; the keychain entry is keyed on the +identifier the kit already uses. See +[Authenticate to external services](../customize/kits.md#authenticate-to-external-services) +for the kit-side wiring. + ### List and remove secrets List all stored secrets: @@ -131,17 +168,15 @@ network policy. For details, see ## Custom secrets > [!IMPORTANT] -> Custom secrets are experimental. The `set-custom` subcommand is -> hidden from `sbx --help`, and behavior, flags, and the placeholder -> format may change. - -For services that aren't in the [supported services](#supported-services) -table and don't fit the service-identifier model used by -[kits](../customize/kits.md#authenticate-to-external-services), -`sbx secret set-custom` stores a secret keyed on a target host and -environment variable name. The sandbox sees the env var set to a -generated placeholder; the proxy substitutes the real secret into -outbound traffic to the target host before forwarding. +> Custom secrets are experimental. The `set-custom` subcommand is hidden +> from `sbx --help`, and behavior, flags, and the placeholder format may +> change. + +For credentials that don't fit the service-identifier model — for example, +when an agent validates the env var format at boot, or when the credential +lands in a request body rather than a header — use `sbx secret set-custom`. +The secret is keyed on a target domain, an env var name, and an optional +placeholder string, instead of a service identifier. ```console $ sbx secret set-custom -g \ @@ -150,18 +185,13 @@ $ sbx secret set-custom -g \ --value ``` -Inside the sandbox, `API_KEY` contains a placeholder string (for -example, `sbx-cs-`). When a sandboxed process sends a request -to `api.example.com` and the placeholder appears anywhere in the -request, the proxy replaces it with the real value. The agent never -sees the real secret. +Inside the sandbox, `API_KEY` is set to a generated placeholder (for example, +`sbx-cs-`). When a sandboxed process sends a request to +`api.example.com` and the placeholder appears anywhere in the request, the +proxy replaces it with the real value. The agent never sees the real secret. -Use `set-custom` only when the service-based flow doesn't fit — for -example, when the credential lands in a request body rather than a -header, when the value is mixed with other text in a header, or -when you don't want to author a kit. Prefer the -[stored secret](#stored-secrets) flow whenever it's an option; it's -simpler and the credential shape is constrained. +Prefer the [service-based flow](#stored-secrets) whenever it's an option — +the kit handles the wiring; you only provide the value. ## Environment variables @@ -180,7 +210,7 @@ The proxy reads the variable from your terminal session. See individual > These environment variables are set on your host, not inside the sandbox. > Sandbox agents are pre-configured to use credentials managed by the > host-side proxy. For custom environment variables not tied to a -> [supported service](#supported-services), see +> [built-in service](#built-in-services), see > [Setting custom environment variables](../faq.md#how-do-i-set-custom-environment-variables-inside-a-sandbox). ## Best practices From df0d808d79d4bcd0ff1596f2f55c3393601bd2a7 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 5 May 2026 16:16:00 +0200 Subject: [PATCH 09/10] sandboxes: address review feedback and fix vale spelling - Drop bold on "template" / "kit" definitions and replace the ambiguous "The two compose." opener. - Reformat the credential-injection bullet list to plain prose instead of the marketing-style `**Term.** Description` pattern. - Add a warning callout on `sbx secret set-custom --value ` about shell history and process visibility. - Replace "subcommand" and "env var" with full forms to clear Vale.Spelling errors in the CI run. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../manuals/ai/sandboxes/customize/_index.md | 14 ++-- .../ai/sandboxes/security/credentials.md | 66 +++++++++++-------- 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/content/manuals/ai/sandboxes/customize/_index.md b/content/manuals/ai/sandboxes/customize/_index.md index fbace2b306c..a5441fca1ee 100644 --- a/content/manuals/ai/sandboxes/customize/_index.md +++ b/content/manuals/ai/sandboxes/customize/_index.md @@ -27,24 +27,24 @@ evolves. Share feedback and bug reports in the ## Templates and kits, side by side -A **template** is a Docker image that the sandbox runs. It's built ahead +A template is a Docker image that the sandbox runs. It's built ahead of time with a Dockerfile (or saved from a running sandbox), pushed to a registry, and pulled when a sandbox is created. Use templates for things that belong in an image: system packages, language toolchains, large dependencies — anything you'd rather not reinstall on every sandbox start. -A **kit** is a YAML artifact applied at sandbox creation. The kit can run +A kit is a YAML artifact applied at sandbox creation. The kit can run install commands, drop files into the sandbox, declare network and credential rules, and (for agent kits) define which template image the agent runs in. Use kits for things that vary per agent or per team: shared linter config, project-specific install steps, credential injection for a service the agent talks to. -The two compose. An agent kit's `agent.image` field points at a template: -the template provides the base environment, the kit layers config, -secrets, and runtime behavior on top. A team can ship one heavy template -and several thin kits without rebuilding the image each time something -changes. +Templates and kits work together. An agent kit's `agent.image` field +points at a template: the template provides the base environment, the +kit layers config, secrets, and runtime behavior on top. A team can ship +one heavy template and several thin kits without rebuilding the image +each time something changes. ## When to use which diff --git a/content/manuals/ai/sandboxes/security/credentials.md b/content/manuals/ai/sandboxes/security/credentials.md index d94b420df6f..9def46167b1 100644 --- a/content/manuals/ai/sandboxes/security/credentials.md +++ b/content/manuals/ai/sandboxes/security/credentials.md @@ -22,33 +22,35 @@ definition) declares the first two. You provide the value on the host. There are two host-side stores, plus a host shell fallback: -- **Stored secrets, by service identifier.** Built-in agents declare service - identifiers (`anthropic`, `openai`, `github`, etc.) in their kit specs; - custom kits can declare their own. `sbx secret set` stores a value keyed on - that identifier. When a sandboxed request matches a service's domain, the - proxy reads the stored value and writes the configured header. Inside the - sandbox, the env var holds a sentinel like `proxy-managed`, so SDKs that - read the variable see something non-empty without seeing the real secret. - See [Stored secrets](#stored-secrets). - -- **Stored secrets, by target domain and env var name.** `sbx secret set-custom` - stores a value alongside a target domain, an env var name, and an optional - placeholder. The sandbox sees the placeholder; the proxy substitutes it - with the real value anywhere it appears in outbound traffic to that - domain. Use this when the service-identifier model doesn't fit — for - example, when the agent validates the env var format at boot, or when the - credential lands in a request body. See [Custom secrets](#custom-secrets). - -- **Host shell environment variables.** As a fallback, the proxy reads from - your shell environment. Useful for one-off testing or development; stored +- Stored secrets, keyed on a service identifier: built-in agents declare + service identifiers (`anthropic`, `openai`, `github`, etc.) in their kit + specs; custom kits can declare their own. `sbx secret set` stores a value + keyed on that identifier. When a sandboxed request matches a service's + domain, the proxy reads the stored value and writes the configured header. + Inside the sandbox, the environment variable holds a sentinel like + `proxy-managed`, so SDKs that read the variable see something non-empty + without seeing the real secret. See [Stored secrets](#stored-secrets). + +- Stored secrets, keyed on a target domain and environment variable name: + `sbx secret set-custom` stores a value alongside a target domain, an + environment variable name, and an optional placeholder. The sandbox sees + the placeholder; the proxy substitutes it with the real value anywhere it + appears in outbound traffic to that domain. Use this when the + service-identifier model doesn't fit — for example, when the agent + validates the variable format at boot, or when the credential lands in a + request body. See [Custom secrets](#custom-secrets). + +- Host shell environment variables: as a fallback, the proxy reads from your + shell environment. Useful for one-off testing or development; stored secrets are preferred because shell environment variables are plaintext and visible to other processes running as your user. See [Environment variables](#environment-variables). -If both a stored secret and a host env var are set for the same service, the -stored secret takes precedence. For multi-provider agents (OpenCode, Docker -Agent), the proxy selects credentials based on the API endpoint being called. -See individual [agent pages](../agents/) for provider-specific details. +If both a stored secret and a host environment variable are set for the same +service, the stored secret takes precedence. For multi-provider agents +(OpenCode, Docker Agent), the proxy selects credentials based on the API +endpoint being called. See individual [agent pages](../agents/) for +provider-specific details. ## Stored secrets @@ -168,15 +170,16 @@ network policy. For details, see ## Custom secrets > [!IMPORTANT] -> Custom secrets are experimental. The `set-custom` subcommand is hidden +> Custom secrets are experimental. The `set-custom` command is hidden > from `sbx --help`, and behavior, flags, and the placeholder format may > change. For credentials that don't fit the service-identifier model — for example, -when an agent validates the env var format at boot, or when the credential -lands in a request body rather than a header — use `sbx secret set-custom`. -The secret is keyed on a target domain, an env var name, and an optional -placeholder string, instead of a service identifier. +when an agent validates the environment variable format at boot, or when the +credential lands in a request body rather than a header — use +`sbx secret set-custom`. The secret is keyed on a target domain, an +environment variable name, and an optional placeholder string, instead of a +service identifier. ```console $ sbx secret set-custom -g \ @@ -185,6 +188,13 @@ $ sbx secret set-custom -g \ --value ``` +> [!WARNING] +> Passing the secret as `--value ` records it in your shell history +> and exposes it to other processes running as your user. Avoid pasting +> real credentials inline — read the value from a variable that's already +> in your environment, and clear shell history if a real secret was passed +> on the command line. + Inside the sandbox, `API_KEY` is set to a generated placeholder (for example, `sbx-cs-`). When a sandboxed process sends a request to `api.example.com` and the placeholder appears anywhere in the request, the From 75c16941b8c0f1b34846b6753849ac5fe95905eb Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 5 May 2026 16:19:08 +0200 Subject: [PATCH 10/10] sandboxes: spell out "repository" instead of "repo" Vale.Spelling flags bare "repo" in prose. The Docker style guide also prefers the full word per Docker.RecommendedWords. Co-Authored-By: Claude Opus 4.7 (1M context) --- content/manuals/ai/sandboxes/customize/build-an-agent.md | 2 +- content/manuals/ai/sandboxes/customize/kit-examples.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/manuals/ai/sandboxes/customize/build-an-agent.md b/content/manuals/ai/sandboxes/customize/build-an-agent.md index 2e045830c2b..735590297d9 100644 --- a/content/manuals/ai/sandboxes/customize/build-an-agent.md +++ b/content/manuals/ai/sandboxes/customize/build-an-agent.md @@ -266,7 +266,7 @@ $ sbx run --kit ./amp/ amp ``` The published copy of this kit also runs directly from the contrib -repo: +repository: ```console $ sbx run --kit "git+https://github.com/docker/sbx-kits-contrib.git#dir=amp" amp diff --git a/content/manuals/ai/sandboxes/customize/kit-examples.md b/content/manuals/ai/sandboxes/customize/kit-examples.md index 411298c5e2a..1027ea8f4b5 100644 --- a/content/manuals/ai/sandboxes/customize/kit-examples.md +++ b/content/manuals/ai/sandboxes/customize/kit-examples.md @@ -132,7 +132,7 @@ on a runtime value. Use a static file otherwise. > [!TIP] > This snippet is lifted from the > [code-server kit](https://github.com/docker/sbx-kits-contrib/tree/main/code-server) -> in the contrib repo, which is also a runnable sample that demonstrates +> in the contrib repository, which is also a runnable sample that demonstrates > the full pattern. ## Ship a Claude Code skill