Egress and secrets
The env's egress gateway, per-sandbox network policy, and credentials a sandbox can use without being able to read them.
Two decisions, and conflating them is the usual mistake:
SandboxEnv does this environment HAVE a gateway one switch
create call what THIS sandbox may reach, and what per sandbox
may be injected into its outbound requestsThe env carries a switch; the rules belong to an individual sandbox and arrive with the create call, in the E2B SDK's own vocabulary. There is no separate Agent Sandbox dialect for them.
Why the switch is on the env and the rules are not
Enabling the gateway injects a transparent proxy sidecar. That changes the Pod spec, and a changed Pod spec rolls the env's pools — a real, environment-level decision, taken once. Rules are per sandbox because an env is shared: an env-wide allowlist would be a default every caller overrides anyway.
It fails closed. A create that carries filtering rules against an env with
no gateway is refused with 400, not accepted and ignored. A Pod with no
sidecar has no redirection, so accepting the rules would mean they silently did
nothing — which for an evaluation is the worst outcome, because the run
finishes and the numbers are wrong.
abx update envs YOUR_ENV --help --cluster YOUR_CLUSTER # the env's one gateway fieldCutting a sandbox off, and letting one thing through
The common case is an agent under test that must not fetch the answer and must not install its way around a missing dependency. The shape is always the same: deny everything, then allow what the task genuinely needs. A denylist is a list of the routes somebody thought of.
# nothing in, nothing out
sbx = Sandbox.create("YOUR_ENV", timeout=3000, secure=False, allow_internet_access=False)
# or: everything out except these
sbx = Sandbox.create(
"YOUR_ENV",
timeout=3000,
secure=False,
network={"allow_out": ["api.openai.com", "pypi.org", "*.pythonhosted.org"]},
)Naming an allow list is what makes everything else a deny — the entries are the whole of what is reachable. A CIDR or a bare IP works in the same list.
An empty allow list is not a deny
network={"allow_out": []} declares no filtering at all, and egress stays
unrestricted. To cut a sandbox off, say so: allow_internet_access=False, or
deny_out=["0.0.0.0/0"].
Three things worth checking before calling a run isolated:
- The env has a gateway. Without it, a create carrying rules is refused —
so make sure you saw a sandbox, not a
400. - The package index is not on the allowlist unless the task needs it. It is
the most common accidental hole: the agent cannot search, but it can
pip installsomething that can. - Watch it fail from inside.
sbx.commands.run("curl -sS -m 5 https://example.com") # expected: it failsAn isolation you have not seen fail is an isolation you are assuming.
Credentials a sandbox can use but cannot read
vault (write-only) → operator memory → sidecar tmpfs → outbound headerThe value never enters the sandbox. The sandbox holds a decoy — a
placeholder that looks like a token — and the egress sidecar substitutes the
real one as the request leaves, for the hosts and headers the rule names. Code
inside runs unmodified: it reads OPENAI_API_KEY, sends it, and the sidecar
replaces it on the way out. Library code that has never heard of Agent Sandbox
works.
Secrets live in a vault that is write-only: you can list names, overwrite
them and delete them, never read them back. In the console it is the Vault
page; through the SDK it is the E2B /secrets surface, which the official
package speaks as e2b.Secret (that module arrived in e2b 2.43).
Store it once
abx envs YOUR_ENV docs --cluster YOUR_CLUSTER # API URL and data-plane domainimport os
os.environ["E2B_API_KEY"] = "agbx_..." # your platform key
os.environ["E2B_API_URL"] = "https://YOUR_GATEWAY/agent-sandbox/api/e2b"
os.environ["E2B_DOMAIN"] = "YOUR_GATEWAY/agent-sandbox/api/data" # no scheme
from agent_sandbox_e2b import patch_e2b # before the e2b import
patch_e2b()
from e2b import Secret
Secret.create("openai-api-key", os.environ["OPENAI_API_KEY"]) # write-only
# rotate it later with Secret.update("openai-api-key", new_value)Use it at create time
Reference it by name; the value never leaves the vault. Secret.fill() is a
local formatting helper — it returns the string the wire carries, and makes no
call of its own:
from e2b import Sandbox, Secret
sbx = Sandbox.create(
"YOUR_ENV",
timeout=3000,
secure=False,
# What the code inside reads. It is a decoy: the real value is put in on the
# way out, so the sandbox cannot leak what it never had.
envs={"OPENAI_API_KEY": "decoy-not-a-real-key"},
network={
# A rule is a transform, not a permission — the host has to be allowed
# too, or the request never leaves.
"allow_out": ["api.openai.com"],
"rules": {
"api.openai.com": [
{
"transform": {
"headers": {
"Authorization": f"Bearer {Secret.fill('openai-api-key')}",
}
}
}
]
},
},
)Ordinary code inside, no Agent Sandbox dialect:
sbx.commands.run(
'curl -s https://api.openai.com/v1/models -H "Authorization: Bearer $OPENAI_API_KEY"'
) # succeeds
sbx.commands.run("echo $OPENAI_API_KEY") # prints the decoy, not the key
sbx.commands.run("curl -sS -m 5 https://example.com") # fails: not allowed
sbx.kill()A plaintext credential in the rules is refused with 400 — deliberately,
because accepting it would put the value in the request body, the access log
and the caller's source, which is the exposure the feature exists to remove.
Wildcard hosts are refused for the same reason: whoever controls a matching
subdomain would receive the injected credential.
The env has to have the gateway
All of the above is refused on an Env whose gateway is off, with a 400 that
says so:
a network policy was requested but this environment has no egress gateway;
enable it on the SandboxEnv (overrides.gateway.enabled) and let its pools rollThat is the switch from the top of this page, and turning it on is a change to the Pod spec, so it rolls the Env's pools.
When it silently does not work
The sandbox runs, the request goes out, and nothing is substituted. Check in this order:
| Check | Why |
|---|---|
| the env has the gateway on | without it, a create with rules is refused — so if a sandbox exists, this one is satisfied |
| the host matches the rule exactly | a redirect to another host is not covered |
| the port is 80 or 443 | only those are parsed at layer 7; rules for other ports never fire and nothing says so |
| the secret name exists in your vault | a name that resolves to nothing leaves the decoy in place, and the upstream answers 401, which reads like a bad key |
abx whoami tells you which identity the vault is read as; a secret stored by
one user is not visible to another.
Agents, vaults and approval
Writing a vault secret is allowed to an agent credential, under the normal approval gate: the credential lands in the acting person's own vault and widens nobody's authority. Minting an Agent Sandbox API key is the act that is refused. See API key permissions.
See also
- Envs — the gateway switch and the rest of the settings
- Pools — why enabling the gateway rolls them
- In-place update — what a claim does to a Pod