Skip to main content
Skip to main content

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, 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.

The workspace notices changes to these variables without a restart (workspace 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, 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:

# 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.

--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. 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). 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).

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 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 <name> --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:

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:

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.

zbox integration create stripe --target-url https://api.stripe.com --credential-env STRIPE_KEY --inject bearer
zbox integration list
--injectWhat the proxy sends
bearerAuthorization: Bearer <value>
basicAuthorization: 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:<name>The value as is in the header <name>, 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.

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:

# 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 <label>.int.workspace over plain HTTP. The proxy speaks HTTPS to the target:

curl http://stripe.int.workspace/v1/customers

Any Authorization, Proxy-Authorization or X-Api-Key header the sandbox sends is removed before the credential is added. Responses are streamed back as they arrive and aren't filtered, so a target that echoes request headers back shows the credential to the sandbox.

Attaching and detaching on a running sandbox applies at once. A label works only in the sandbox it's attached to. Forks and restored sandboxes get the attachments of their parent, and terminating a sandbox removes its attachments.

Caution

A label attached while the sandbox is parked with memory doesn't resolve after the wake. Attach to running sandboxes, or detach and attach the label again once the sandbox runs.

The proxy makes the request from the workspace, so it works in every network mode except airgapped. A sandbox with project-only network can still reach an internet API through an integration.

When the proxy can't serve a request, it answers with a JSON error:

StatusCodeMeaning
404notAttachedThe sandbox has no attachment with this label.
502credentialMissingThe workspace has no value in the integration's credential variable.
502upstreamFailedThe target didn't answer.
Coming soon

Peer attachments: a label that points at a port of another sandbox, so sandboxes can call each other through the proxy.

Connectors​

A connector is a ready-made provider login (Claude Code, GitHub, OpenAI Codex): it stores the credential in a workspace variable and manages the integrations that the proxy injects it for, and it also sets up the provider's tools inside the sandbox. See connectors.