Agent sandboxes
A sandbox is a container or microVM you start from your own code to run something you do not fully trust — model output, a coding agent, a user's snippet. That makes it a different secrets problem from a deploy target, and a better one: your process is on the outside, holding the token, deciding what goes in.
Every provider here supports the same two shapes. They are not variants of each other — they answer different questions.
| Inject at boot | Keep the credential outside | |
|---|---|---|
| What the sandbox gets | The real values, as environment variables | Placeholders, and an endpoint |
| Code you trust inside | Your own, or an agent you supervise | Anything, including hostile output |
| If the sandbox is compromised | The key is gone — rotate it | Nothing to steal; the key was never there |
| Effort | Three lines | A proxy or an outbound handler |
| Reach | Everything the value is good for | Only the hosts and operations you allowed |
Reach for inject at boot when the sandbox exists for isolation from your
machine — a build, a test run, a notebook — and the code inside is code you
would have run anyway. Reach for keep the credential outside when the whole
point of the sandbox is that you do not trust what runs in it. An agent that can
read os.environ can exfiltrate a key it finds there, and "it only pipes it to
the model" stops being true the moment the model writes the code.
Cloudflare's own Sandbox documentation is blunt about this: "Do not put live
API keys or other long-lived credentials into the sandbox." That is the second
column, and it is the same pattern seekrit ships as
seekrit-proxy — a credential the workload names
but never holds.
The shape that is the same everywhere
Whichever provider you use, injecting at boot is the same three steps, and none of them run inside the sandbox:
import seekrit, os
# 1. Resolve and decrypt on the host — your process, your token.
secrets = seekrit.Client(token=os.environ["SEEKRIT_TOKEN"]).resolve()
# 2. Hand the sandbox exactly what it needs, not the whole environment.
envs = {k: secrets[k] for k in ("OPENAI_API_KEY", "TAVILY_API_KEY")}
# 3. Start it.
sandbox = create_sandbox(envs=envs) # provider-specific; see the pages below
The ordering is what makes it safe. The service token never enters the sandbox, so code inside cannot re-resolve the environment, ask for a different one, or reach seekrit at all — it gets the values you chose and nothing else. Decryption happens in your process, which is where seekrit's zero-knowledge model wants it.
Never pass SEEKRIT_TOKEN into a sandbox running code you do not trust.
A service token resolves a whole environment, so handing it over turns "the
agent has two API keys" into "the agent has every secret in this environment,
and can fetch them again after you rotate the two."
If the sandbox genuinely needs to resolve for itself, give it its own token bound to its own environment — or better, a temporary-access lease that expires.
Pick names, not the whole environment
resolve() returns everything the token can see. Injecting all of it is the
easy mistake: a sandbox that needed one model key ends up holding the database
password too, because both live in production.
Two ways to narrow it — prefer the second, which survives code edits:
- In your code, as above — a dict comprehension over the names you meant.
- In seekrit, with an environment scoped to this job. A
sandboxenvironment that composes only the group holding model keys can't leak a database password, because it never had one. The narrowing then survives someone editing the injection code.
Per provider
| Provider | Inject at boot | Keep the credential outside |
|---|---|---|
| E2B | Sandbox.create({ envs }), or per-command | seekrit-proxy as a sidecar, or on the host |
| Modal | Secret.from_dict() at deploy or sandbox create | seekrit-proxy in the image |
| Daytona | envVars at create, updateEnv after | seekrit-proxy on the host |
| Vercel Sandbox | env at create, or per runCommand | networkPolicy: 'deny-all' plus a proxy |
| Cloudflare Sandbox | setEnvVars() or exec({ env }) | Outbound handlers — no proxy needed |
| Cloudflare Computer | exec({ env }) | One egress gateway for all three backends |
| Fly.io Sprites | seekrit-run as the service command | seekrit-proxy as a service, behind the egress policy |
| Boat | env at create, alongside --no-env | seekrit-proxy on your machine, through box forward --reverse |
Cloudflare is the one that does the second column natively: an outbound handler
(Sandbox) or an egress gateway (Computer) runs in the Worker, outside the
sandbox, and attaches the credential on the way past. Everywhere else the same
job is seekrit-proxy.
Sprites is the odd one out in the first column, and it is worth knowing before you pick a provider: a Sprite keeps its filesystem across hibernation and snapshots it into checkpoints. Injecting at create is a one-time act there, so a long-lived Sprite drifts from whatever you rotate, and a credential written to its disk comes back with every restore. Its page leads with resolving at service start for that reason.
Boat's Boxes persist the same way, across stop/resume and into snapshots,
forks and templates, so its page leads with resolving at process start too. Boat
also ships a secrets store of its own, which raises a question no other provider
here does: whether to keep a second plaintext copy in it at all.
What about a hosted agent runtime?
If the platform runs the agent for you — you never start a process — there is no host to resolve on and none of this applies. That is a third-party sync problem instead: seekrit decrypts on its own servers and pushes the values to the platform, which is a real trade-off and the one case seekrit's zero-knowledge rule is explicitly carved out for. LangGraph Platform is the agent-hosting destination.
The line is simply whether you control a process at startup. A sandbox you create from your own code: you do — use this page. A managed Agent Server: you don't — sync to it.
Some runtimes let you choose, and those are worth checking before you assume
either answer. OpenAI's Agents API
runs the loop and the model at OpenAI while letting you pick where the code
runs: environment.type: "self_hosted" is this page (you start an executor, so
you can wrap it with seekrit run), while environment.type: "openai_hosted"
uses OpenAI's sandbox. In the latter, use OpenAI vault credentials or a
placeholder and a proxy you run to keep real values out of model-authored code.
See also
- AI frameworks — the same shapes in LangGraph's, Mastra's, or Pydantic AI's idiom, for the agent you are running inside the sandbox, starting from a three-command setup
- OpenAI Agents API — a hosted harness with a self-hosted sandbox option, and what to do when the sandbox is theirs
seekrit run— the same injection for a process on your own machine- Agent proxy — the credential the workload never holds
- Agent access policy — bounding which hosts and operations an agent may reach
- Temporary access — a credential that expires on its own
- SDKs —
resolve()in Python, Go, JavaScript and Ruby