Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions content/manuals/ai/sandboxes/agents/claude-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ Claude Code requires either an Anthropic API key or a Claude subscription.
$ sbx secret set -g anthropic
```

Alternatively, export the `ANTHROPIC_API_KEY` environment variable in your
shell before running the sandbox. See
[Credentials](../security/credentials.md) for details on both methods.
You can also source the key from the `ANTHROPIC_API_KEY` environment variable
through a [credential binding](../security/credentials.md#credential-bindings);
the sandbox prompts you to approve one on first run. See
[Credentials](../security/credentials.md) for details.

**Claude subscription**: If no API key is set, use the `/login` command inside
Claude Code to authenticate via OAuth.
Expand Down
5 changes: 3 additions & 2 deletions content/manuals/ai/sandboxes/agents/codex.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ so browser-based authentication works without any extra setup.
$ sbx secret set -g openai
```

Alternatively, export the `OPENAI_API_KEY` environment variable in your shell
before running the sandbox.
You can also source the key from the `OPENAI_API_KEY` environment variable
through a [credential binding](../security/credentials.md#credential-bindings);
the sandbox prompts you to approve one on first run.

See [Credentials](../security/credentials.md) for more details.

Expand Down
8 changes: 5 additions & 3 deletions content/manuals/ai/sandboxes/agents/copilot.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ Copilot requires a GitHub token with Copilot access. Store your token using
$ echo "$(gh auth token)" | sbx secret set -g github
```

Alternatively, export the `GH_TOKEN` or `GITHUB_TOKEN` environment variable in
your shell before running the sandbox. See
[Credentials](../security/credentials.md) for details on both methods.
You can also source the token from the `GH_TOKEN` or `GITHUB_TOKEN` environment
variable through a
[credential binding](../security/credentials.md#credential-bindings); the
sandbox prompts you to approve one on first run. See
[Credentials](../security/credentials.md) for details.

## Configuration

Expand Down
7 changes: 4 additions & 3 deletions content/manuals/ai/sandboxes/agents/cursor.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ Cursor supports two authentication methods: an API key or OAuth.
$ sbx secret set -g cursor
```

Alternatively, export the `CURSOR_API_KEY` environment variable in your shell
before running the sandbox. See
[Credentials](../security/credentials.md) for details on both methods.
You can also source the key from the `CURSOR_API_KEY` environment variable
through a [credential binding](../security/credentials.md#credential-bindings);
the sandbox prompts you to approve one on first run. See
[Credentials](../security/credentials.md) for details.

**OAuth**: If no API key is set, Cursor prompts you to sign in interactively
on first run. The proxy intercepts the token exchange with
Expand Down
9 changes: 5 additions & 4 deletions content/manuals/ai/sandboxes/agents/docker-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ $ sbx secret set -g openrouter
You only need to configure the providers you want to use. Docker Agent detects
available credentials and routes requests to the appropriate provider.

Alternatively, export the environment variables (`OPENAI_API_KEY`,
You can also source these from environment variables (`OPENAI_API_KEY`,
`ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`, `XAI_API_KEY`, `NEBIUS_API_KEY`,
`MISTRAL_API_KEY`, `OPENROUTER_API_KEY`) in your shell before running the
sandbox. See
[Credentials](../security/credentials.md) for details on both methods.
`MISTRAL_API_KEY`, `OPENROUTER_API_KEY`) through
[credential bindings](../security/credentials.md#credential-bindings); the
sandbox prompts you to approve one per provider on first run. See
[Credentials](../security/credentials.md) for details.

## Configuration

Expand Down
7 changes: 4 additions & 3 deletions content/manuals/ai/sandboxes/agents/droid.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ your Factory account.
$ sbx secret set -g droid
```

Alternatively, export the `FACTORY_API_KEY` environment variable in your shell
before running the sandbox. See
[Credentials](../security/credentials.md) for details on both methods.
You can also source the key from the `FACTORY_API_KEY` environment variable
through a [credential binding](../security/credentials.md#credential-bindings);
the sandbox prompts you to approve one on first run. See
[Credentials](../security/credentials.md) for details.

**OAuth**: If no API key is set, Droid prompts you to authenticate
interactively on first run. The proxy handles the OAuth flow, so credentials
Expand Down
8 changes: 5 additions & 3 deletions content/manuals/ai/sandboxes/agents/gemini.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ Gemini requires either a Google API key or a Google account with Gemini access.
$ sbx secret set -g google
```

Alternatively, export the `GEMINI_API_KEY` or `GOOGLE_API_KEY` environment
variable in your shell before running the sandbox. See
[Credentials](../security/credentials.md) for details on both methods.
You can also source the key from the `GEMINI_API_KEY` or `GOOGLE_API_KEY`
environment variable through a
[credential binding](../security/credentials.md#credential-bindings); the
sandbox prompts you to approve one on first run. See
[Credentials](../security/credentials.md) for details.

**Google account**: If no API key is set, Gemini prompts you to sign in
interactively when it starts. Interactive authentication is scoped to the
Expand Down
10 changes: 6 additions & 4 deletions content/manuals/ai/sandboxes/agents/opencode.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ $ sbx secret set -g openrouter
You only need to configure the providers you want to use. OpenCode detects
available credentials and offers those providers in the TUI.

You can also use environment variables (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,
`GOOGLE_GENERATIVE_AI_API_KEY`, `XAI_API_KEY`, `GROQ_API_KEY`,
`AWS_ACCESS_KEY_ID`, `OPENROUTER_API_KEY`). See
[Credentials](../security/credentials.md) for details on both methods.
You can also source these from environment variables (`OPENAI_API_KEY`,
`ANTHROPIC_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY`, `XAI_API_KEY`,
`GROQ_API_KEY`, `AWS_ACCESS_KEY_ID`, `OPENROUTER_API_KEY`) through
[credential bindings](../security/credentials.md#credential-bindings); the
sandbox prompts you to approve one per provider on first run. See
[Credentials](../security/credentials.md) for details.

## Configuration

Expand Down
8 changes: 5 additions & 3 deletions content/manuals/ai/sandboxes/agents/shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ $ sbx run shell -- -c "echo hi" # runs bash -l -c "echo hi"

When the first argument is a bare word, it replaces `-l` instead.

Set your API keys as environment variables so the sandbox proxy can inject
them into API requests automatically. Credentials are never stored inside
the VM:
Provide your API keys as environment variables so the sandbox proxy can inject
them into API requests. The proxy injects a key once a
[credential binding](../security/credentials.md#credential-bindings) authorizes
it — the sandbox prompts you to approve one on first run. Credentials are never
stored inside the VM:

```console
$ export ANTHROPIC_API_KEY=sk-ant-xxxxx
Expand Down
139 changes: 122 additions & 17 deletions content/manuals/ai/sandboxes/security/credentials.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,22 @@ declares the match and the header; you provide the value on the host. The real
value never enters the sandbox — the agent sees only a sentinel like
`proxy-managed`.

There are several ways to provide that value. When more than one source has a
value for the same service, the stored secret takes precedence over a host
environment variable.

| Form | What it is | Use it when |
| ---- | ---------- | ----------- |
| [Stored secrets](#stored-secrets) (`sbx secret set`) | A value in your OS keychain, keyed by service | The default for any built-in or kit-declared service |
| [Custom secrets](#custom-secrets) (`sbx secret set-custom`) | A value keyed to a domain and environment variable | The service model doesn't fit — the agent validates the variable's format, or the secret rides in a request body |
| [Environment variables](#environment-variables) | Read from your shell session | One-off testing or CI, where keychain storage isn't worth it |
| OAuth | A host-side sign-in flow; the token never enters the sandbox | The agent supports it, such as Claude Code, Codex, or Cursor |
| [Registry credentials](#registry-credentials) (`sbx secret set --registry`) | Authentication for pulling images and kits | Pulling templates or kits from a private registry |
There are several ways to provide that value. For built-in agents, a
[credential bindings](#credential-bindings) entry authorizes each credential: it
records where the value is sourced from and which domains it may be injected
into. `sbx` creates this entry interactively the first time an agent needs the
credential. Without an authorizing binding, the credential is withheld rather
than injected. When a binding resolves more than one source, the stored secret
takes precedence over an environment variable or file.

| Form | What it is | Use it when |
| --------------------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| [Stored secrets](#stored-secrets) (`sbx secret set`) | A value in your OS keychain, keyed by service | The default for any built-in or kit-declared service |
| [Custom secrets](#custom-secrets) (`sbx secret set-custom`) | A value keyed to a domain and environment variable | The service model doesn't fit — the agent validates the variable's format, or the secret rides in a request body |
| [Environment variables](#environment-variables) | A shell variable a binding's discovery points at | One-off testing, where keychain storage isn't worth it |
| OAuth | A host-side sign-in flow; the token never enters the sandbox | The agent supports it, such as Claude Code, Codex, or Cursor |
| [Credential bindings](#credential-bindings) (`credentials.yaml`) | Per-service sourcing and domain approval | The default authorization for built-in agents; also restricts which domains a credential reaches |
| [Registry credentials](#registry-credentials) (`sbx secret set --registry`) | Authentication for pulling images and kits | Pulling templates or kits from a private registry |

For multi-provider agents (OpenCode, Docker Agent), the proxy selects
credentials based on the API endpoint being called. See individual
Expand Down Expand Up @@ -236,24 +241,124 @@ the kit handles the wiring; you only provide the value.

## Environment variables

As an alternative to stored secrets, export the relevant environment variable
in your shell before running a sandbox:
A host environment variable isn't a credential source on its own — built-in
agents don't read host variables implicitly, so exporting `ANTHROPIC_API_KEY`
and running `sbx run claude` does nothing by itself. To use one, point a
[credential binding](#credential-bindings) at it, listing the variable under the
binding's `discovery`. `sbx` prompts you to create that binding the first time
an agent needs the credential, or you can write it yourself.

With a binding in place, export the variable before you run the sandbox. See
individual [agent pages](../agents/) for the variable names each agent expects:

```console
$ export ANTHROPIC_API_KEY=sk-ant-api03-xxxxx
$ sbx run claude
```

The proxy reads the variable from your terminal session. See individual
[agent pages](../agents/) for the variable names each agent expects.

> [!NOTE]
> These environment variables are set on your host, not inside the sandbox.
> These environment variables are read on your host, not set 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
> [built-in service](#built-in-services), see
> [Setting custom environment variables](../faq.md#how-do-i-set-custom-environment-variables-inside-a-sandbox).

## Credential bindings

A credential bindings file records, per service, where `sbx` finds each
credential value and which domains it may be injected into. It lives at
`~/.config/sbx/credentials.yaml`, or `%APPDATA%\sbx\credentials.yaml` on
Windows.

Built-in agents require an authorizing binding for each credential they use.
`sbx` creates one interactively the first time you run an agent (see
[First-run approval](#first-run-approval)); you can also write entries by hand.

Each entry under `bindings` is keyed by a
[service identifier](#built-in-services) and has two parts:

- `discovery` — where to find the value: one or more environment variables,
or a file. Entries are tried in order. Omit `discovery` to resolve the value
from the [secret store](#stored-secrets) as usual.
- `allowedDomains` — the domains the proxy may inject this credential into.
The credential is never attached to a domain outside this list, even if a kit
declares it.

```yaml
bindings:
anthropic:
discovery:
- env: [ANTHROPIC_API_KEY]
allowedDomains: [api.anthropic.com]
github:
discovery:
- env: [GH_TOKEN, GITHUB_TOKEN]
allowedDomains: [api.github.com, github.com]
```

For a file source, set `parser: json:<dot.path>` to pull a field from a JSON
file, or omit `parser` to use the whole file — see the
[file parser format](../customize/kit-reference.md#fileparser) in the kit spec
reference. Bindings
apply to services a kit or built-in agent already declares; they control how an
existing service's credential is sourced and scoped, not which services exist.

For example, to source the GitHub token from a field in a JSON file:

```yaml
bindings:
github:
discovery:
- file:
path: "~/.config/myapp/creds.json"
parser: "json:credentials.github.token"
allowedDomains: [api.github.com, github.com]
```

### First-run approval

Built-in agents inject a credential only where a binding approves it. The first
time an agent needs a credential that has no binding, `sbx` walks you through
creating one. For an API key, you choose where the value comes from (the secret
store, an environment variable, or a file) and approve the domains it may reach.
For OAuth, you approve the sign-in domains and authenticate in the host flow —
there's no source to pick. Either way, `sbx` writes the entry to
`credentials.yaml`, and the same prompt appears in the terminal and in the
interactive TUI.

In non-interactive contexts (CI or `--detached`), there's no one to answer the
prompt, so a missing binding is reported as a clear error naming the service
rather than a silently absent credential. Pre-create the binding — by running
the agent interactively once, or by writing `credentials.yaml` directly — before
running unattended.

This makes the bindings file an allowlist of credential-to-domain approvals: an
agent can use only the credentials you've approved, only on the domains you've
approved.

<!-- TODO(launch, confirm before publish): upgrade experience for users who

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Unresolved TODO comment gating publish-readiness on an open issue

This HTML comment explicitly marks a section as incomplete and instructs "confirm before publish," gated on docker/sandboxes#3684. While the PR is intentionally drafted and held, if this PR is merged before that issue is resolved, the upgrade-experience documentation gap will silently ship — HTML comments are invisible to readers but the missing content is not.

Recommend tracking this as a blocker before merging (or ensuring the TODO is resolved inline before the PR leaves draft status).

already stored a secret (sbx secret set) before built-ins moved to v2.
Confirm whether the existing stored secret is auto-bound on first run or
whether the user is prompted to approve a binding for it, then add an
"Upgrading from an earlier release" note here. Gated on docker/sandboxes#3684. -->

#### Which kits require a binding

Requiring an approved binding is a property of the kit's `schemaVersion`, not of
whether the agent is built-in. Every built-in agent uses `schemaVersion: "2"`,
and so does any custom kit authored against it — all of them require a binding
and behave identically. Kits still on `schemaVersion: "1"` inject their declared
credentials without a binding.

To hold older-schema kits to the same rule, turn on fail-closed mode:

```console
$ sbx settings set credentials.failClosed true
```

With fail-closed on, every injected credential requires an approved binding,
regardless of the kit's schema.

## Registry credentials

Registry credentials authenticate to private OCI registries when pulling
Expand Down
7 changes: 7 additions & 0 deletions content/manuals/ai/sandboxes/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ If the agent can't reach its model provider or you see API key errors, the key
is likely invalid, expired, or not configured. Verify it's set in your shell
configuration file and that you sourced it or opened a new terminal.

If the agent starts unauthenticated, or a non-interactive run (`--detached` or
CI) fails with a "no approved binding" error, the credential has no
[credential binding](security/credentials.md#credential-bindings). Run the
agent interactively once to approve the binding at the prompt, or pre-create
the entry in `credentials.yaml`. A credential is injected only where a binding
authorizes it.

For agents that use the [credential proxy](security/credentials.md), make sure
you haven't set the API key to an invalid value inside the sandbox — the proxy
injects credentials automatically on outbound requests.
Expand Down