docs: Updated the sandboxes docs with the new and improved sbx credential management

2026-07-24 18:26:58 -06:00
parent e290026b5e
commit 542789ce1d
2 changed files with 89 additions and 169 deletions
+73 -92
@@ -5,7 +5,8 @@ your host machine.
Sandbox mode is powered by [Docker Sandboxes](https://docs.docker.com/ai/sandboxes/). Coyote does not implement its own
container layer. It delegates everything to the `sbx` CLI: lifecycle, isolation, networking, image management, and
process supervision. Coyote's only job is to orchestrate the bootstrap (build a sandbox of the right shape, copy your
config + vault credentials in, attach you to a running session) and then get out of the way.
config in, inject your LLM and MCP credentials into the sbx secret store, attach you to a running session) and then get
out of the way.
## Quick Start
@@ -18,14 +19,8 @@ coyote --sandbox
# Or with an explicit name
coyote --sandbox my-project
# Create without copying your host config (clean-slate sandbox)
# Start fresh — no copied config or MCP secrets; LLM credentials still injected via proxy
coyote --sandbox throwaway --fresh
# Skip all sbx mixin discovery and application (debugging / minimum sandbox)
coyote --sandbox --no-mixins
# True bare-bones, isolated sandbox with fresh Coyote install
coyote --sandbox --fresh --no-mixins
```
The first run pulls the pre-built Coyote sandbox image from Docker Hub (`darkalex17/coyote`) and creates the sandbox.
@@ -34,7 +29,7 @@ Attaches to an existing sandbox are instant: your config, vault state, sessions,
persist inside the sandbox until you `sbx rm` it.
Re-running `coyote --sandbox [NAME]` with the same name **re-attaches** to the existing sandbox silently rather than
creating a fresh one. `--fresh` and `--no-mixins` are ignored on re-attach (they only affect sandbox creation).
creating a fresh one. `--fresh` is ignored on re-attach (it only affects sandbox creation).
### Multiple sandboxes per workspace
@@ -69,41 +64,44 @@ coyote --info | grep -i "sbx_kit_dir" | awk '{print $2}'
# 2. Discover mixins:
# - Walk known discovery paths for user-authored sbx-mixin.yaml files
# - Inspect your secrets_provider type; if non-Local, also extract the
# matching built-in vault-provider mixin to:
# $XDG_CACHE_HOME/coyote/sbx-vault-mixins/<provider>/
# 3. Log what's about to be applied (info! and println!). See "Verbose
# mixin log" below.
# 4. Check if a sandbox with this name already exists
# 4. Inject your LLM API credentials into the sbx global secret store (host-side).
# Coyote reads your host config, finds any {{SECRET_NAME}} placeholder in your
# client's api_key, decrypts it from your host vault, and registers it:
sbx secret set -g <llm-provider> # e.g. anthropic, openai, google
# (skipped silently if already registered; see "Updating a registered secret")
# 5. Inject MCP secrets into the sbx global secret store (host-side, skipped if --fresh).
# Coyote scans your mcp.json for {{SECRET_NAME}} placeholders anywhere in each server
# config (env, headers, or any nested field), decrypts on the host, and registers each:
sbx secret set -g <mcp-service>
# (each secret skipped silently if already registered)
# 6. Check if a sandbox with this name already exists
sbx ls
# 5. If not, create it with the base kit + every discovered mixin layered on
# 7. If not, create it with the base kit + every discovered mixin layered on
sbx create \
--name <NAME> \
--kit <cache>/sbx-kit/ \
--kit <vault-provider-mixin-if-any> \
--kit <user-mixin-1> \
--kit <user-mixin-N> \
coyote .
# 6. Copy your host config into the sandbox (skipped if --fresh). Each top-level
# entry is copied individually to sidestep a macOS `docker cp` quirk that
# silently drops files carrying `com.apple.provenance` xattrs when they're
# tarred as part of a recursive directory copy.
# 8. Copy your host config into the sandbox (skipped if --fresh). vault.yml is
# excluded. The vault is not used inside the sandbox. Each top-level entry is
# copied individually to sidestep a macOS `docker cp` quirk that silently drops
# files carrying `com.apple.provenance` xattrs when tarred as a recursive copy.
sbx exec <NAME> sh -c "sudo mkdir -p /home/agent/.config/coyote && sudo chown agent:agent /home/agent/.config/coyote"
for entry in ~/.config/coyote/*; do
sbx cp "$entry" <NAME>:/home/agent/.config/coyote/
done
sbx exec <NAME> sh -c "sudo chown -R agent:agent /home/agent/.config/coyote"
# 7. Copy your vault password file, if a local provider is configured (skipped if --fresh)
sbx exec <NAME> sh -c "sudo mkdir -p <parent> && sudo chown agent:agent <parent>"
sbx cp <host-password-file> <NAME>:<destination>
sbx exec <NAME> sh -c "sudo chown -R agent:agent <destination>"
# 8. Hand control to sbx (Coyote's process is replaced). `--kit` is re-passed
# 9. Hand control to sbx (Coyote's process is replaced). `--kit` is re-passed
# on reattach because sbx expects it even when the sandbox already exists.
exec sbx run --name <NAME> --kit <cache>/sbx-kit/
```
@@ -111,21 +109,19 @@ exec sbx run --name <NAME> --kit <cache>/sbx-kit/
Once `sbx run` takes over, Coyote on the host exits and your terminal is connected to Coyote inside the sandbox. All
signals (Ctrl-C, etc.) flow straight through.
> Coyote handles steps 13, 6, 7, and the `--name` argument of step 5. Everything else is `sbx` doing its job.
> Coyote handles steps 15, 8, and the `--name` argument of step 7. Everything else is `sbx` doing its job.
### Verbose mixin log
Before `sbx create` runs, Coyote emits a single block to both the log and stdout naming every mixin about to be applied:
```
Applying 3 sbx mixin(s):
<built-in: vault-one_password> (adds: 1 install, 6 domains)
Applying 2 sbx mixin(s):
~/.config/coyote/functions/sbx-mixin.yaml (adds: 0 installs, 20 domains)
~/.config/coyote/agents/my-python-dev/sbx-mixin.yaml (adds: 1 install, 1 domain)
```
If zero mixins were discovered, you'll see `No sbx mixins discovered.` in the log (no terminal noise). If you launched
with `--no-mixins`, you'll see `Mixin discovery disabled via --no-mixins.` instead.
If zero mixins were discovered, you'll see `No sbx mixins discovered.` in the log (no terminal noise).
Skim this log on first launch and after installing any [shared configuration bundle](Sharing-Configurations). It's your
audit point for what each mixin grants in terms of installs and network domain allowances.
@@ -147,71 +143,59 @@ Coyote intentionally does not wrap sandbox lifecycle commands. Use `sbx` directl
Run `sbx --help` for the full surface. None of these are reimplemented in Coyote.
## Vault Behavior
## Credentials & Secrets
Coyote's sandbox bootstrap is aware of your [vault](Vault) configuration and behaves differently depending on which provider you've chosen:
The vault is **not used inside a sandbox**. Instead, Coyote injects your LLM API credentials and MCP secrets directly
into the sbx global secret store **on your host** before the sandbox starts. Inside the sandbox, the sbx network proxy
intercepts outbound HTTP requests and supplies the registered credentials automatically. Your API provider receives the
correct key without Coyote ever decrypting anything inside the VM.
### Local provider (default)
### How injection works
Coyote resolves your `vault_password_file` (or `secrets_provider.password_file`) from your host config and copies it into the sandbox at the matching location. The path is rewritten when it lives under `$HOME` (so `~/.coyote_password` on your host becomes `/home/agent/.coyote_password` in the sandbox), but kept verbatim for absolute paths outside `$HOME` (so `/etc/coyote/.coyote_password` is copied to `/etc/coyote/.coyote_password` inside the sandbox). No further action is needed — your vault works on first launch.
Before `sbx create`, Coyote:
Additionally, at vault initialization time *inside* the sandbox, Coyote auto-retranslates any `vault_password_file` (or `secrets_provider.password_file`) that points to a host home path (`/home/<X>/...`, `/Users/<X>/...`, or `C:\Users\<X>\...`) to the corresponding `/home/agent/...` path. This means custom password-file locations under your host's home directory (e.g. `/home/atusa/.config/coyote/.password`, `/Users/atusa/...`, or `C:\Users\atusa\...`) resolve correctly in the sandbox without any config rewriting — your config stays pristine. An `INFO`-level log line is emitted whenever this translation kicks in, so you can verify the resolution if needed.
1. Reads your host `config.yaml` and locates any `{{SECRET_NAME}}` placeholder in your configured client's `api_key` field.
2. Decrypts that secret from your host vault.
3. Registers it globally with `sbx secret set -g <service>` (e.g. `anthropic`, `openai`, `google`).
4. Unless `--fresh` is passed, repeats the same process for every `{{SECRET_NAME}}` placeholder found anywhere in your `mcp.json` server configurations (including `env`, `headers`, and any other nested fields).
#### Host OS support for the password file copy
Secrets already registered with `sbx` are silently skipped to keep re-attaches fast. See
[Updating a registered secret](#updating-a-registered-secret) if you've rotated a credential.
| Host OS | Password file under `$HOME`/`%USERPROFILE%` | Password file outside `$HOME`/`%USERPROFILE%` |
|---------|--------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Linux | Copied to `/home/agent/<rel>` | Copied verbatim (e.g. `/etc/coyote/.password` -> `/etc/coyote/.password`) |
| macOS | Copied to `/home/agent/<rel>` | Copied verbatim |
| Windows | Copied to `/home/agent/<rel>` (with backslashes normalized to forward slashes) | **Refused with a clear error**. Windows paths outside `%USERPROFILE%` (e.g. `C:\Program Files\Coyote\vault.txt`) can't be projected into a Linux sandbox. Move the file under your user profile (`C:\Users\<you>\...`). |
If your LLM client uses **OAuth** instead of an API key (`auth: oauth` in your config), no secret is injected. The
sbx proxy handles OAuth natively without a stored key.
### Non-Local provider (auto-installed CLI mixin)
### First-run wizard inside the sandbox
If `secrets_provider.type` is anything other than `local`, Coyote automatically applies a built-in mixin that
**installs the corresponding provider CLI** inside the sandbox. The mixin also allowlists the domains the CLI needs to
authenticate:
If the sandbox has no Coyote config (e.g. after `--fresh` or on a brand-new sandbox), Coyote runs a lightweight
first-run wizard when it starts inside the sandbox:
| Provider | CLI installed | Auto-applied mixin |
|-----------------------|---------------|-----------------------------------------|
| `one_password` | `op` | `<built-in: vault-one_password>` |
| `azure_key_vault` | `az` | `<built-in: vault-azure_key_vault>` |
| `gopass` | `gopass` | `<built-in: vault-gopass>` |
| `aws_secrets_manager` | `aws` (v2) | `<built-in: vault-aws_secrets_manager>` |
| `gcp_secret_manager` | `gcloud` | `<built-in: vault-gcp_secret_manager>` |
1. Pick your API provider.
2. For providers that support OAuth (Claude, OpenAI, Gemini, and any openai-compatible provider with bundled OAuth), optionally choose OAuth instead of an API key.
3. Pick a model.
The mixin installs the CLI but **does not log you in** — see the next section.
The resulting `config.yaml` contains no secrets. Credentials are still managed entirely by the sbx proxy.
### Re-Authenticating Your Vault in a Sandbox
### Updating a registered secret
After the sandbox is created, you must authenticate the provider CLI inside the sandbox once. Either use the [built-in
REPL command passthrough](REPL#command---run-an-arbitrary-shell-command) to execute the login command in the sandbox
when already open to the REPL, or use the Docker sbx CLI directly via `sbx exec <NAME> <command>`:
| Provider | First-time sandbox auth |
|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `local` | Nothing (password file auto-copied) |
| `one_password` | <ul><li>**Command Passthrough:** `> !op signin` </li><li>**Docker Sbx CLI:** `sbx exec <NAME> op signin` </li></ul> |
| `azure_key_vault` | <ul><li>**Command Passthrough:** `> !az login` </li><li>**Docker Sbx CLI:** `sbx exec <NAME> az login` </li></ul> |
| `gopass` | <ul><li>**Command Passthrough:** `> !gopass clone <your-git-remote>` (or `!gopass setup`) </li><li>**Docker Sbx CLI:** `sbx exec <NAME> gopass clone <your-git-remote>` (or `gopass setup`) </li></ul> |
| `aws_secrets_manager` | <ul><li>**Command Passthrough:** `> !aws configure sso` (or `!aws sso login --profile <profile>`) </li><li>**Docker Sbx CLI:** `sbx exec <NAME> aws configure sso` (or `aws sso login --profile <profile>`) </li></ul> |
| `gcp_secret_manager` | <ul><li>**Command Passthrough:** `> !gcloud auth application-default login` </li><li>**Docker Sbx CLI:** `sbx exec <NAME> gcloud auth application-default login` </li></ul> |
These commands authenticate **once per sandbox**, meaning credentials persist on the sandbox filesystem until you
`sbx rm <NAME>`.
If you forget to re-auth, the vault will fail to decrypt the first time Coyote needs a secret inside the sandbox, and
you'll see a clear error from `gman` pointing at the right login command.
### Manual credential transfer (alternative)
If you'd rather not re-authenticate inside the sandbox, you can transfer your host credentials directly:
Coyote skips secrets that are already registered to avoid unnecessary prompts on every sandbox launch. If you've
rotated a credential, update it manually on your host:
```bash
sbx cp ~/.aws <NAME>:/home/agent/.aws
sbx cp ~/.config/gcloud <NAME>:/home/agent/.config/gcloud
# See what's currently registered
sbx secret ls
# Overwrite a specific secret (supply the new value via stdin)
echo "sk-new-value" | sbx secret set -g --force anthropic
```
This is faster but bleeds host state into the sandbox. It's your call. Coyote intentionally doesn't do this automatically.
The updated credential takes effect immediately; you do not need to restart the sandbox.
### Vault operations inside the sandbox
Vault management commands (`coyote --add-secret`, `--update-secret`, `--delete-secret`, `--list-secrets`, and `.vault`
REPL commands) are **disabled inside the sandbox** and will return an error directing you to your host. Manage your
vault from your host machine.
## Extending the Sandbox: Auto-Discovered Mixins
@@ -268,8 +252,6 @@ Coyote already ships mixins for the most common needs. They get auto-applied whe
- **Built-in tools mixin** (auto-applied when `coyote --install functions` has run). Allowlists the domains used by
every built-in global tool and the default MCP server set: Wikipedia, arxiv, jina, wttr, WolframAlpha, Perplexity,
Tavily, Twilio, github MCP, atlassian MCP, ddg-search MCP, npm registry, Docker registries.
- **Vault provider mixins** (auto-applied when `secrets_provider.type` matches). See [Vault Behavior](#vault-behavior) above.
One for each of 1Password, Azure, gopass, AWS, and GCP.
You can list everything that's about to be applied via the [verbose mixin log](#verbose-mixin-log) on every launch.
@@ -300,21 +282,20 @@ every Coyote sandbox, so running `coyote --sandbox` from within a sandbox is a n
If something looks wrong, these are your three debugging flags:
| Flag | Effect | Use when… |
|----------------------|-------------------------------------------------------------|---------------------------------------------------------------------------|
| `--no-mixins` | Skips all mixin discovery (built-in vault + user-authored) | A mixin is causing `sbx create` to fail and you want to bisect |
| `--fresh` | Skips host config + vault password file copy | You suspect your host config is contaminating the sandbox; isolation test |
| `COYOTE_SANDBOX_KIT` | Points at an alternate base kit instead of the embedded one | Developing a fork of the kit or hardening for a specific environment |
| Flag | Effect | Use when… |
|----------------------|------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
| `--fresh` | Skips host config copy and MCP secret injection; LLM credentials still injected via proxy | You want a clean-slate sandbox with no copied state |
| `COYOTE_SANDBOX_KIT` | Points at an alternate base kit instead of the embedded one | Developing a fork of the kit or hardening for a specific environment |
### Common failures
- **A mixin causes `sbx create` to abort.** Re-run with `--no-mixins` to confirm the base kit works. Then check the
verbose mixin log to identify which mixin was being applied when it failed. Open that mixin file and inspect its
`commands.install` block.
- **A mixin causes `sbx create` to abort.** Check the verbose mixin log to identify which mixin was being applied when
it failed. Open that mixin file and inspect its `commands.install` block. Temporarily move the file aside to confirm.
- **A malformed `sbx-mixin.yaml` aborts launch before `sbx create` runs.** Coyote fails fast with a parse error naming
the file. Fix the YAML or temporarily move it aside.
- **Vault decryption fails inside the sandbox.** You forgot to re-auth your non-Local provider. See
[Re-Authenticating Your Vault in a Sandbox](#re-authenticating-your-vault-in-a-sandbox).
- **An API request from inside the sandbox fails with an auth error.** The secret may not have been registered or may
be stale. Run `sbx secret ls` on your host to check, then see
[Updating a registered secret](#updating-a-registered-secret).
- **A custom tool fails with `command not found`.** The tool's required binary isn't installed in the sandbox. Add it
to the matching mixin (per-tool, per-agent, or top-level user mixin). See [Custom Tools](Custom-Tools#sandbox-support).
- **A network request from inside the sandbox hangs or fails.** The domain isn't in any `allowedDomains` block. Add it
@@ -333,6 +314,6 @@ If something looks wrong, these are your three debugging flags:
## See Also
- [Docker Sandboxes: Official Docs](https://docs.docker.com/ai/sandboxes/)
- [Vault](Vault): How Coyote resolves the password file that gets copied in
- [Vault](Vault): How Coyote's vault works; vault management is disabled inside sandboxes
- [Environment Variables](Environment-Variables): Including `COYOTE_SANDBOX_KIT`
- [Clients](Clients#authentication): Auth flows you may need to redo inside the sandbox for OAuth providers
+16 -77
@@ -125,86 +125,25 @@ If you set up Coyote with one provider and later want to switch, just edit your
# Vault in Sandboxes
If you run Coyote inside a [Docker Sandbox](Sandboxes), your vault provider determines what (if anything) you need to do
to make it usable in the sandbox.
The vault is **not active** inside a [Docker Sandbox](Sandboxes). Coyote injects your LLM API credentials and MCP
secrets directly into the sbx global secret store **on your host** before the sandbox starts, using
`sbx secret set -g <service>`. Inside the sandbox, the sbx network proxy intercepts outbound HTTP requests and
supplies the registered credentials automatically; no vault password file is copied, and no vault provider CLI is
installed or configured inside the VM.
## Local provider
This means:
Nothing is required. Coyote auto-copies your vault password file into the sandbox at the matching location during
`coyote --sandbox`. Your existing config and encrypted vault.yml come along for the ride. See
[Sandboxes — Vault Behavior](Sandboxes#vault-behavior) for the exact path-rewriting rules.
- **No vault password file is copied into the sandbox.**
- **No vault provider CLI (op, az, aws, gcloud, gopass) is installed or configured in the sandbox.**
- **Vault management commands are disabled inside the sandbox.** `coyote --add-secret`, `--update-secret`,
`--delete-secret`, `--list-secrets`, and `.vault` REPL commands all return an error. Manage your vault from your
host machine.
- **Secrets added to your vault after a sandbox was created are not automatically propagated.** See
[Updating a registered secret](Sandboxes#updating-a-registered-secret) for how to push a new value without
recreating the sandbox.
If your `vault_password_file` (or `secrets_provider.password_file`) is set to a path under your *host's* home
directory (e.g. `/home/atusa/.config/coyote/.password`, `/Users/atusa/...` on macOS, or `C:\Users\atusa\...`
on Windows), Coyote inside the sandbox automatically retranslates that path to the corresponding
`/home/agent/...` location at vault initialization time. So your existing config doesn't need to be edited,
and a `coyote --info` inside the sandbox will still show the original path you configured. You'll see an
`INFO`-level log line noting the translation if it happens. This means custom password-file locations work
transparently in the sandbox.
### Windows-host limitation
On Windows hosts, the password file must live under `%USERPROFILE%` (i.e. `C:\Users\<you>\...`). If your
configured `vault_password_file` points anywhere outside the user profile (e.g. `C:\Program Files\Coyote\vault.txt`),
`coyote --sandbox` will refuse to start with a clear error directing you to move the file. The Linux sandbox
has no way to faithfully represent paths under `C:\Program Files\` or other Windows-only locations, so the file
copy can't proceed.
Linux and macOS hosts have no such restriction. Vault files outside the home directory are copied verbatim
into the sandbox at the same absolute path (e.g. `/etc/coyote/.password``/etc/coyote/.password` inside
the sandbox).
## Non-Local providers (one-time re-authentication needed)
For every other provider, Coyote auto-applies a built-in **sbx mixin** that installs the corresponding CLI inside the
sandbox. The mixin does **not** log you in! You must re-authenticate once per sandbox lifetime:
| Provider | First-time sandbox auth |
|-----------------------|--------------------------------------------------------------------------------------|
| `local` | Nothing (password file auto-copied) |
| `one_password` | `sbx exec <sandbox-name> op signin` |
| `azure_key_vault` | `sbx exec <sandbox-name> az login` |
| `gopass` | `sbx exec <sandbox-name> gopass clone <your-git-remote>` (or `gopass setup`) |
| `aws_secrets_manager` | `sbx exec <sandbox-name> aws configure sso` (or `aws sso login --profile <profile>`) |
| `gcp_secret_manager` | `sbx exec <sandbox-name> gcloud auth application-default login` |
Replace `<sandbox-name>` with whatever you passed to `coyote --sandbox <name>` (or the basename of your project
directory if you used the no-argument form).
### From inside the running Coyote REPL (alternative)
When `coyote --sandbox` is already running and you're at the REPL prompt inside the sandbox, you can run the auth
command directly with the [`!<command>` shell pass-through](REPL#command---run-an-arbitrary-shell-command) without opening a second terminal:
```
> !op signin
> !az login
> !gopass clone <your-git-remote>
> !aws configure sso
> !gcloud auth application-default login
```
The output streams to your REPL, Ctrl-C cancels, and you stay in the conversation. This is usually quicker than
exiting Coyote to run `sbx exec <sandbox-name> <command>` from your host shell. Use `sbx exec` when you're not
already inside the REPL, or when you want to launch an interactive sub-shell to step through a complex login flow.
## Things to know
- **Re-auth is per-sandbox, not per-Coyote-launch.** Once you authenticate, credentials persist inside the sandbox
filesystem until you `sbx rm <sandbox-name>`. Reopening the sandbox later does not re-prompt.
- **`sbx rm <sandbox-name>` wipes all cached credentials.** The next `coyote --sandbox <sandbox-name>` creates a fresh
sandbox that requires re-auth.
- **The built-in mixin installs the CLI but does not pre-configure it.** Users with multiple AWS profiles, Azure
subscriptions, GCP projects, or 1Password accounts must run the same `aws configure` / `az account set` /
`gcloud config set` / `op account add` they would on a fresh host.
- **gopass with self-hosted git remotes:** the built-in mixin only allows `github.com` and `gitlab.com` for cloning
your store. If you host the store elsewhere (Bitbucket, self-hosted GitLab, etc.), add the relevant host to your
own top-level `~/.config/coyote/sbx-mixin.yaml` under `network.allowedDomains`. Without this, `gopass clone` will
fail with a network error.
- **Manual credential transfer is supported as an alternative.** If you'd rather not re-authenticate inside the
sandbox, you can `sbx cp ~/.aws <sandbox-name>:/home/agent/.aws` (or equivalent) before starting Coyote. Coyote
intentionally does _not_ do this automatically. Re-auth inside the sandbox keeps host state out of the sandbox by
default. The choice is yours.
For the full injection sequence and details on the sandbox first-run wizard, see
[Sandboxes: Credentials & Secrets](Sandboxes#credentials--secrets).
# Motivation
Coyote is intended to be highly configurable and adaptable to many different use cases. This means that users of Coyote