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

That ordering is the point. 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, and the second is better:

  • 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 is the one that does the second column natively: an outbound handler runs in the Worker, outside the sandbox, and attaches the credential on the way past. Everywhere else the same job is seekrit-proxy.

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.

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
  • 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
  • SDKsresolve() in Python, Go, JavaScript and Ruby