How to use API keys in a sandbox without exposing them
Store the key with runtime secrets set NAME --host api.example.com; sandboxes see a placeholder and Runtime adds the value in transit.
On Runtime the sandbox never holds the key: not in its environment, its memory or its disk, so a prompt injection or a stolen sandbox has nothing to leak. Each secret names the hosts it may go to. The proxy on the host puts the value into HTTPS requests to those hosts and nowhere else, and code that sends the placeholder anywhere else sends something worthless. One stored secret serves every sandbox of the account, with up to 50 secrets per account.
Store a secret
Terminalprintf %s "$OPENAI_API_KEY" | runtime secrets set OPENAI_API_KEY --host api.openai.comruntime secrets lsThe CLI reads the value from standard input only, so it never lands in your
shell history, and typed at a terminal it is not shown. ls shows names, hosts
and placeholders, never values.
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.secrets.set("OPENAI_API_KEY", { value: process.env.OPENAI_API_KEY ?? "", hosts: ["api.openai.com"],});Pythonimport osfrom withruntime import Runtimeruntime = Runtime()runtime.secrets.set("OPENAI_API_KEY", value=os.environ.get("OPENAI_API_KEY", ""), hosts=["api.openai.com"])An agent connected through MCP uses runtime_secrets_set,
runtime_secrets_list and runtime_secrets_delete.
Use it from code in the sandbox
Nothing changes in the code. Every sandbox has an environment variable of the
secret's name, holding a placeholder such as rtsec_3f9c…, and an SDK that
reads OPENAI_API_KEY sends it as usual:
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create();const call = await sbx.exec( 'curl -sS https://api.openai.com/v1/models -H "Authorization: Bearer $OPENAI_API_KEY"', { timeoutMs: 30_000 },);console.log(call.exitCode);const seen = await sbx.exec("printenv OPENAI_API_KEY");console.log(seen.stdout); // rtsec_…, the placeholder, never the keyPythonfrom withruntime import Sandboxwith Sandbox.create() as sbx: call = sbx.exec('curl -sS https://api.openai.com/v1/models ' '-H "Authorization: Bearer $OPENAI_API_KEY"', timeout_ms=30_000) print(call.exit_code) print(sbx.exec("printenv OPENAI_API_KEY").stdout) # the placeholderSet the header for the code
With header, the proxy sets that header on every HTTPS request to the hosts,
replacing one the sandbox sent, so code needs no placeholder at all. format
says where the value goes:
Terminalruntime secrets set GITHUB_TOKEN --host api.github.com --header Authorization --format 'token {value}' < token.txtTypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.secrets.set("GITHUB_TOKEN", { value: process.env.GITHUB_TOKEN ?? "", hosts: ["api.github.com", "*.githubusercontent.com"], header: "Authorization", format: "token {value}",});A secret with header Authorization and format AWS4-HMAC-SHA256 {value}
holds a bucket's ACCESS_KEY_ID:SECRET_ACCESS_KEY and signs S3 requests again
with the real key, so the secret key is never sent at all
(mount your own bucket).
Limits
| What | Limit |
|---|---|
| Secrets per account | 50 |
| Value | At most 8 KiB of visible ASCII |
| Hosts per secret | 16, as names or *.domain |
| Where it is added | URL and headers of HTTPS requests to the secret's hosts |
| Never added | Plain HTTP, request bodies, any other host |
| Protocol to its hosts | HTTP/1.1, WebSockets included; gRPC works to every other host |
| Replacing a value | Keeps the placeholder; running sandboxes use it within seconds |
Mistakes and how Runtime handles them
- Passing the key in
envor the command line.envvalues are never echoed back and journals keep only a hash, but the sandbox then holds the key. A command line is recorded as sent. Use a secret instead. - Naming a host that echoes requests. A host that reflects headers back would show the value to the sandbox. Name only hosts you trust with it.
- A program with pinned certificates. The proxy opens the HTTPS connection
to the secret's hosts itself and checks the real server's certificate.
Python, Node, curl and git already trust Runtime's certificate through
SSL_CERT_FILE,REQUESTS_CA_BUNDLEandNODE_EXTRA_CA_CERTS; a program with its own list needs/usr/local/share/ca-certificates/runtime-egress.crtadded to it. - Deleting to revoke.
runtime secrets rmerases the value on Runtime; it does not revoke the key at its provider. Rotate it there too. - A database password. Only HTTPS requests are rewritten, so a Postgres password cannot be swapped this way. For your own cloud, an identity token avoids storing a key at all.
Lock it down further
A secret stops the key from leaking; network rules stop the data. Allow only the secret's host and the sandbox can call that API and nothing else. For no network at all, turn the internet off.
Related
Facts on this page were checked on 25 September 2026.