# Secrets and the credentials proxy A sandbox gets a credential in one of two ways. A **secret** is delivered into the sandbox as an environment variable, so the code inside holds the real value. An **integration** keeps the credential outside: the sandbox calls the [credentials proxy](#credentials-proxy), which adds the credential to the request on its way out. Both take their values from the environment variables of the workspace service. Use the proxy wherever the credential is an HTTP header sent to an API, so an agent can't read or leak it. Use secrets when a tool needs the value itself, for example a database URL. ## Where secrets come from Credentials live in the environment variables of the Box Workspace service in Zerops: your own service variables (use secret variables for sensitive values), the project variables the service sees, and references to other services' variables. You add and edit them in the Zerops GUI or in the import YAML, like for any service, see [environment variables](/features/env-variables). The workspace notices changes to these variables without a restart ([workspace environment variables](/zbox/workspace-service#environment-variables)). Sandboxes and integrations refer to the variables by name only. The workspace keeps no copy of a value, only a fingerprint of what each sandbox was given. Variables whose names start with `ZBOX_`, such as the [workspace API key](/zbox/access#api-key), are never delivered to a sandbox or injected by the proxy. ## Secrets Choose the variables a sandbox gets when you create it. Each `--secret` names one variable exactly, there are no patterns: ```bash # deliver two workspace variables into the sandbox zbox new agent --secret DATABASE_URL --secret STRIPE_KEY ``` A name the workspace doesn't have is skipped without an error. If you add the variable later, the sandbox becomes [stale](#stale-secrets). `--secrets all` delivers every variable you defined for the service, including the project variables it sees. Zerops system variables are left out. Any change to one of them makes the sandbox stale, so prefer the list of names. The selection is part of the sandbox's policy, so you can change it later with `zbox policy` or in the Secrets section of the console's Policy tab. The change only takes effect when the secrets are [redelivered](#stale-secrets). Templates don't carry a selection: you choose the secrets for each sandbox, including sandboxes created from a template. ### How secrets reach the sandbox Secrets are set as environment variables, under the same names, for the sandbox's main process and every command you start in it: `zbox exec`, shells, SSH sessions and detached processes ([running commands](/zbox/commands)). They aren't written to the sandbox's disk. When a sandbox is parked with memory, they are part of its memory image, which is stored on the workspace's encrypted disk. They are delivered when the sandbox is created (from a base or from a warm template), on every wake that boots it from its disk, and on a wake with `--redeliver-secrets`. A wake that resumes the memory image keeps the values the sandbox already had. If you deliver a connector's own variable, for example `--secret GH_TOKEN`, the sandbox holds the real credential and the proxy isn't involved ([connectors](/zbox/connectors)). ### Secret-bearing sandboxes A sandbox that received at least one secret is **secret-bearing** (`secretBearing` in the API, `delivered, no fork` in `zbox get`). A secret-bearing sandbox can't be [forked](/zbox/lifecycle#fork-a-sandbox) or snapshotted with memory, both fail with `secretBearing`, so its secrets can't spread into other sandboxes. Disk snapshots work. :::caution A sandbox restored from a disk snapshot of a secret-bearing sandbox is marked secret-bearing but starts without the secrets in its environment. To deliver them, park it with `zbox park --no-memory` and wake it again. ::: ### Stale secrets A sandbox is **stale** when its selection would now pick something different from what it was given: a selected variable changed its value, was added or removed, or you changed the selection. `zbox get` shows `STALE`, the API shows `secretsStale: true`. A running stale sandbox keeps working with the values it has. A parked one doesn't wake on its own any more: commands, shells, SSH and requests to its ports fail with `secretsStale` (the router answers `503 SECRETS_STALE`), and so does a plain `zbox wake`. Wake it explicitly with the current values: ```bash zbox wake agent --redeliver-secrets ``` In the console, it's the **Wake and redeliver secrets** button on the sandbox page. Redelivery boots the sandbox from its disk: the memory image is discarded, so running processes start again. On a running sandbox, `--redeliver-secrets` does nothing. To give it new values, shut it down first: ```bash zbox park agent --no-memory zbox wake agent --redeliver-secrets ``` ## Credentials proxy The **credentials proxy** runs in the workspace and calls an API on a sandbox's behalf. The sandbox sends a plain HTTP request without any credential. The proxy checks that the sandbox has the integration attached, reads the credential from the workspace's environment variable, adds it to the request and forwards it to the target. The credential never enters the sandbox: it isn't in its environment or on its disk. ### Integrations An **integration** is a named target that the proxy can call: a target URL, the name of the workspace variable that holds the credential, and how the proxy sends it. Integrations belong to the workspace, and you attach them to the sandboxes that may use them. ```bash zbox integration create stripe --target-url https://api.stripe.com --credential-env STRIPE_KEY --inject bearer zbox integration list ``` | `--inject` | What the proxy sends | |---|---| | `bearer` | `Authorization: Bearer ` | | `basic` | `Authorization: Basic` from a `user:password` value. A value without a colon is used as the password of the user `x-access-token`, which GitHub accepts for tokens. | | `header:` | The value as is in the header ``, for example `header:X-Api-Key` | The target is an `http` or `https` URL, on the internet or a service in your project, for example `http://api:3000`. The request path is appended to the target URL's path. With `--strip-prefix /api`, a request for `/api/v1/items` is forwarded as `/v1/items`. The proxy reads the credential on every request. A new value of the variable, or a change made with `zbox integration set`, applies to the next request, without touching any sandbox. An integration can't be deleted while a sandbox has it attached. The name of an integration is a lowercase DNS label: letters, digits and hyphens. :::note Coming soon Rate limits per integration, so the proxy caps how many requests sandboxes send to a target. ::: ### Attach an integration A sandbox uses an integration through an attachment under a **label**, which is the integration's name unless you pick another one. Attach integrations when you create the sandbox, or later: ```bash # at create: the label is the integration name, or label=integration zbox new agent --integration stripe --integration pay=stripe # later, on a running sandbox zbox attach agent stripe zbox attach agent billing --integration stripe zbox attachments agent zbox detach agent billing ``` In the console, the same is the sandbox's Integrations tab, and the workspace's integrations are on the Integrations page. Inside the sandbox, an attached integration is the host `