Scoped secrets are rolling out progressively. The declaration side is live: setting url always mints a handle and keeps the credential out of the sandbox. Redemption depends on support in the infrastructure serving your session, which is not yet enabled everywhere. Where it is not, the agent receives a handle that the destination rejects, which shows up as authentication failures against the bound host rather than as a spec error. Verify that a scoped secret authenticates end to end before relying on one for a production workload.
Secrets and Configurationpublic
Last verified 21 Sep 2026
DigitalOcean Harness Runtime combines the functionality of a lightweight microVM, built-in tools like chromium and a coding sandbox needed by agents to do work. The product offers rich lifecycle APIs that persist conversational history and working state across sessions, with pause/resume/fork semantics so that developers can control costs and adapt workflows to the nonlinear quirks of agentic work. See What You Can Build for example use cases.
An agent that opens a pull request needs a GitHub token. One that calls a model needs an API key. One that files a ticket needs a Jira credential. Those values have to reach the sandbox somehow, and how you declare them in the environment spec decides how they are stored, whether anyone can read them back, and how much damage a leaked one does.
There are three ways to supply a value, and they differ in one way that matters more than the rest: whether the agent can read the real credential.
env |
secrets |
Scoped secrets | |
|---|---|---|---|
| Intended for | Configuration such as model names, endpoints, and feature flags | Credentials the agent uses directly | Credentials for one specific HTTPS destination |
| Storage | Kept verbatim in the saved spec | Held in a managed secret store | Held in a managed secret store |
| Visible in API responses | Yes | No | No |
| Readable inside the sandbox | Yes | Yes | No |
Choosing Between env and secrets
The first rule is short: if the value would matter to an attacker who had it, it belongs in secrets. A model name in secrets costs you nothing. An API key in env is stored in plaintext and handed back in API responses to anyone who can read the environment.
env:
MODEL_TIER: standard
secrets:
OPENAI_API_KEY: ${OPENAI_API_KEY}What a Secret Protects
Notice the last row of the table. An ordinary secret is readable inside the sandbox, the same as plain configuration. This surprises people, and it is worth being precise about.
Declaring a value under secrets protects it everywhere outside the sandbox. It is encrypted at rest in the secret store, stripped from the spec that gets saved, absent from API responses, absent from the event stream, and absent from the Control Panel. If someone gets a copy of your environment spec, they get a reference, not your key.
It protects nothing inside the sandbox. The value is injected as an environment variable so the agent can use it, which means the agent can also print it, write it to a file, or send it to any host egress permits. Any process the agent starts can read it too.
That leaves two ways to reduce what a leaked credential is worth. You can narrow what the credential itself can do, covered under Scoping Credentials. Or you can keep the credential out of the sandbox entirely, which is what scoped secrets do.
Scoped Secrets
A scoped secret binds a credential to a single HTTPS destination. The agent can use it against that destination and cannot read it, copy it, or send it anywhere else.
You declare one by adding a url to the slot:
secrets:
OPENAI_API_KEY:
value: ${OPENAI_API_KEY}
url: https://api.openai.comvalue is the credential, the same as any other secret. url names the one destination it is good for. Both live inside the slot, so url is indented under OPENAI_API_KEY rather than sitting beside it.
What changes is delivery. An ordinary secret is resolved and injected into the sandbox as an environment variable holding the real credential. A scoped secret puts a short-lived authorization handle in that variable instead, under the same slot name, and the credential itself never leaves the secret store. When the sandbox makes a request to the bound host, the egress proxy redeems the handle and attaches the real credential to the outbound request.
The agent’s code does not change. Something that reads OPENAI_API_KEY and calls api.openai.com keeps working. The same code reading the same variable and posting it to some other service leaks a handle that is worthless anywhere except the destination you named.
What the URL Accepts
| Rule | Detail |
|---|---|
| Scheme | https:// only. |
| Host | One exact hostname. Wildcards, IP literals, localhost, and metadata addresses are all rejected. A hostname matches only itself, not its subdomains. |
| Port | Not allowed. |
| Path | Optional, and treated as a prefix rather than an exact match. It must start with / and stay under 256 characters, and it cannot contain ?, #, or %. A bare / means the same as omitting the path. |
| Query or fragment | Not allowed. |
| Credentials in the URL | Not allowed. A user:password@ prefix is rejected. |
Rules and Limits
An environment can declare at most 16 scoped secrets.
A scoped secret cannot also set env: false. The handle has to reach the guest environment, because that is how the agent presents it.
You do not need to add the bound host to your egress allowlist. Declaring a scoped secret adds its host for you, which is the one case where naming a credential also opens a network destination.
Rotation and Revocation
Rotating a scoped credential means updating the value on the environment. Running sessions pick up the new value within the platform’s caching interval rather than needing a restart.
There is no separate way to revoke a handle. A handle stays redeemable until the credential behind it is rotated or deleted, and handles are long-lived by design so that a paused session still works when it resumes. Rotating the underlying secret is what invalidates outstanding handles, so treat that as the response if you believe a sandbox was compromised.
Scoping Credentials
Scoped secrets control where a credential can go. The scope you request at the provider controls what it can do, and the two are worth combining. A token that can only read one repository can only ever read one repository, however it is delivered.
When you issue a credential for an agent:
- Grant the narrowest set of permissions that lets the task succeed, and nothing held in reserve for later.
- Prefer a token scoped to specific repositories, projects, or channels over an account-wide one.
- Issue a separate credential per environment rather than reusing one everywhere, so you can revoke one without breaking the others.
- Set an expiry where the provider supports it.
A worked example is the codex-agentapi adapter, which needs two OpenAI keys with deliberately different reach. One authenticates your own requests to create and retrieve OpenAI sessions, and needs the api.agents.read, api.agents.write, and api.responses.write permissions. The other is an OpenAI environment key used only by the executor connection, and every permission on it is set to None, because its environment connection permission is assigned automatically.
Only the second one goes into the sandbox:
secrets:
CODEX_API_KEY: ${OPENAI_EXECUTOR_API_KEY}
env:
CODEX_ENVIRONMENT_ID: ${ENV_ID}The key with real permissions stays on your side. The key inside the sandbox can do one thing. Both keys must have the same owner, organization, and project. See Run an OpenAI Agents API Session.
Keeping Credentials Out of Everything Else
The secret store only helps with values that go through it. Credentials also leak through the paths around it, so keep them out of container images, the workspace, logs, and source control.
One path is specific to Harness Runtime and worth calling out: a spec written with ${VAR} placeholders is safe to commit, but the resolved manifest, after substitution, contains the real values. Do not save or log a manifest after variable substitution.
How a Secret Reaches the Sandbox
You declare a secret in the spec and supply its value through an environment variable on your machine:
secrets:
OPENAI_API_KEY: ${OPENAI_API_KEY}Harness Runtime expands the value at submission, moves it into the secret store, and records a pinned reference to it. The value is removed from the spec that gets saved. When a session starts, the platform resolves the reference and delivers it, as plaintext for an ordinary secret or as a handle for a scoped one.
This is why an Environment Config can start sessions without you resupplying credentials, and why the secret is captured when the config is created rather than when a session starts from it.
Credential Detection
Putting a credential in env is a common mistake, and Harness Runtime looks for it. Three checks run when you submit a spec:
- A key in
envthat you also declared undersecretsis rejected. - A key whose name reads like a credential is flagged. That covers names containing
API_KEY,TOKEN,SECRET,PASSWORD,PRIVATE_KEY, orCREDENTIAL, names ending in_PAT, and specific names such asOPENAI_KEY,ANTHROPIC_KEY, andAWS_ACCESS_KEY_IDthat the general patterns miss. - A value that matches a known credential format, such as the
sk-,ghp_,dop_v1_, orAKIAprefixes, is flagged.
Flagged specs currently produce a warning and are accepted. Treat the warning as an error and move the value to secrets, because this check is expected to become a hard rejection.
Reserved Environment Variables
Harness Runtime injects its own variables into the sandbox to identify the session, describe the machine it is running on, and carry the permission policy, tool configuration, and telemetry settings. A spec that sets one of these names under env or secrets is rejected at submission rather than being allowed to override platform configuration.
| Purpose | Reserved names |
|---|---|
| Session and environment identity | TEAM_ID, SESSION_ID, SANDBOX_ID, HARNESS_CONFIG_ID |
| Sandbox provisioning | SANDBOX_TEMPLATE, SANDBOX_IMAGE_ID, SANDBOX_OCI_REF, SANDBOX_AGENT_WORKDIR, SIZE_SLUG, SANDBOX_CREATE_MICROVM_AT, SANDBOX_GUEST_BOOT_AT, CONTROL_PLANE_CALLBACK_URL |
| Permission policy | PLANO_PERMISSION_POLICY_B64, PLANO_PERMISSION_DESCRIPTOR_DIGEST |
| Tools, skills, and repositories | HARNESS_MCP_SERVERS, HARNESS_SKILLS, HARNESS_WORKSPACE_REPOS |
| Telemetry and the event stream | OTEL_EXPORTER_INSIGHTS_ENDPOINT, OTEL_EXPORTER_INTERNAL_OBS_ENDPOINT, OHP_FORWARDER_ENABLED, OHP_FORWARDER_ENDPOINT, OHP_FORWARDER_AUTHORITY, OHP_FORWARDER_ACKED_STREAM |
Your agent can read these, and some are useful: SESSION_ID and TEAM_ID are convenient for tagging your own logs. Treat them as read-only.
The list grows as the platform adds configuration, and a name reserved later is rejected on an environment that previously accepted it. Prefixing your own variables with something specific to your application keeps you clear of it.
Secrets for MCP Servers
An MCP server declared in the spec authenticates with a secret you reference by name. The reference must resolve to a secret that reaches the sandbox environment, because the agent’s MCP client reads it from there. Declaring the server’s credential as a secret that is not exposed to the environment fails at session creation rather than producing a server that cannot authenticate.
Tools That Never Receive a Key
Action Gateway reaches the same goal as a scoped secret by a different route. You connect a provider once at the gateway, and the gateway brokers the credential at execution time: the agent asks for a tool call, the gateway resolves the key from its own store, runs the call, and returns the result. Nothing is declared in the environment and nothing is injected into the sandbox, not even a handle. Reach for a gateway connection before a raw token when the provider is one it supports. See Connections.
What Is Recorded
Harness Runtime records which secrets a spec declared and where each came from, so you can audit what a session had access to. It does not record the values. Nothing in the API, the saved spec, the event stream, or the Control Panel returns a secret value after you submit it.
Credentials are one of several controls on a session. For how they fit with workload isolation, egress, permissions, and cleanup, see Secure Sessions.