diff --git a/content/manuals/ai/sandboxes/customize/_index.md b/content/manuals/ai/sandboxes/customize/_index.md index 6b3de68986c..a5441fca1ee 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. + +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 | Goal | Option | diff --git a/content/manuals/ai/sandboxes/customize/build-an-agent.md b/content/manuals/ai/sandboxes/customize/build-an-agent.md index b8560d89221..735590297d9 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 +repository: + +```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..1027ea8f4b5 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 repository, 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 476ee8366ef..b37997d8ec5 100644 --- a/content/manuals/ai/sandboxes/customize/kits.md +++ b/content/manuals/ai/sandboxes/customize/kits.md @@ -98,50 +98,74 @@ 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. 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 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 standard pattern uses four blocks tied to a service identifier +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 # Overwrite this header + valueFormat: "Bearer %s" + credentials: sources: my-service: env: - - MY_SERVICE_API_KEY + - MY_SERVICE_API_KEY # Host-side credential lookup + +environment: + proxyManaged: + - MY_SERVICE_API_KEY # Set the in-VM env var to "proxy-managed" ``` +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. + +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 Agent kits declare an `agent:` block with the image the agent runs in and @@ -209,12 +233,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 @@ -309,7 +337,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 @@ -328,6 +356,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: diff --git a/content/manuals/ai/sandboxes/faq.md b/content/manuals/ai/sandboxes/faq.md index d4ccef99e19..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 @@ -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. diff --git a/content/manuals/ai/sandboxes/security/credentials.md b/content/manuals/ai/sandboxes/security/credentials.md index e038d0df048..9def46167b1 100644 --- a/content/manuals/ai/sandboxes/security/credentials.md +++ b/content/manuals/ai/sandboxes/security/credentials.md @@ -8,32 +8,55 @@ 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, 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 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 -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,27 +84,43 @@ 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 | -| ----------- | -------------------------------------------- | ----------------------------------- | -| `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 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: @@ -128,6 +167,42 @@ 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` 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 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 \ + --host api.example.com \ + --env API_KEY \ + --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 +proxy replaces it with the real value. The agent never sees the real secret. + +Prefer the [service-based flow](#stored-secrets) whenever it's an option — +the kit handles the wiring; you only provide the value. + ## Environment variables As an alternative to stored secrets, export the relevant environment variable @@ -145,7 +220,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