seekrit
Docs/Agent sandboxes

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 bootKeep the credential outside
What the sandbox getsThe real values, as environment variablesPlaceholders, and an endpoint
Code you trust insideYour own, or an agent you superviseAnything, including hostile output
If the sandbox is compromisedThe key is gone — rotate itNothing to steal; the key was never there
EffortThree linesA proxy or an outbound handler
ReachEverything the value is good forOnly 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.

note

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.

caution

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 sandbox environment 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

ProviderInject at bootKeep the credential outside
E2BSandbox.create({ envs }), or per-commandseekrit-proxy as a sidecar, or on the host
ModalSecret.from_dict() at deploy or sandbox createseekrit-proxy in the image
DaytonaenvVars at create, updateEnv afterseekrit-proxy on the host
Vercel Sandboxenv at create, or per runCommandnetworkPolicy: 'deny-all' plus a proxy
Cloudflare SandboxsetEnvVars() or exec({ env })Outbound handlers — no proxy needed
Cloudflare Computerexec({ env })One egress gateway for all three backends
Fly.io Spritesseekrit-run as the service commandseekrit-proxy as a service, behind the egress policy
Boatenv at create, alongside --no-envseekrit-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