docs/sandboxes/secrets.mdx
Secrets use a placeholder substitution model. The guest VM never sees the real credential.
When you bind a secret to an environment variable and one or more allowed hosts, microsandbox generates a random placeholder (e.g., OPENAI_API_KEY=msb_ph_a8f3c2...) and injects that into the guest instead. The real value never enters the VM. The only way it reaches the outside world is when a request goes to an allowed host, at which point microsandbox swaps the placeholder for the real value. Everywhere else, the placeholder is just a meaningless string.
So even with full code execution inside the sandbox, there's nothing to steal. The credential was never there.
Allowed hosts are checked against both TLS identity and DNS history. For intercepted HTTPS, the SNI must match the allow-list pattern, the original guest destination IP must have been returned by the sandbox DNS interceptor for that host, and the HTTP request authority must match the SNI (Host for HTTP/1, :authority for observed HTTP/2). Empty allow-lists are rejected; use the explicit any-host escape hatch only when the network policy already prevents exfiltration.
Secret environment variable names follow Linux environment-entry rules: they must be non-empty and cannot contain = or NUL, but microsandbox does not require shell-identifier syntax. Custom placeholders must be non-empty, may be up to 1024 bytes, and cannot contain NUL, CR, or LF. The proxy uses the 1024-byte bound to detect and substitute placeholders split across TLS reads and HTTP chunks without buffering unbounded request data.
When body injection is enabled, HTTP/1 request bodies are handled according to their framing. Content-Length bodies up to 16 MiB are buffered until complete and the Content-Length header is rewritten if the body length changes; larger fixed-length bodies are blocked rather than buffered unboundedly. Transfer-Encoding: chunked bodies are decoded, rewritten, and re-encoded with fresh chunk sizes while preserving trailers. Bodies with a non-identity Content-Encoding are forwarded unchanged. HTTP/2 header, query, and Basic-auth placeholders are decoded and rewritten when HTTP/2 HEADERS frames are observed; HTTP/2 DATA-frame body substitution is not supported yet, so matching body placeholders are blocked instead of being forwarded unchanged.
let sb = Sandbox::builder("agent") .image("python") .secret(|s| s .env("GITHUB_TOKEN") .value(std::env::var("GITHUB_TOKEN")?) .allow_host("api.github.com") .allow_host_pattern("*.githubusercontent.com") ) .secret_env("OPENAI_API_KEY", api_key, "api.openai.com") .create() .await?;
```typescript TypeScript
import { Sandbox } from "microsandbox";
await using sb = await Sandbox.builder("agent")
.image("python")
.secret((s) =>
s.env("GITHUB_TOKEN")
.value(process.env.GITHUB_TOKEN!)
.allowHost("api.github.com")
.allowHostPattern("*.githubusercontent.com"),
)
.secretEnv("OPENAI_API_KEY", process.env.OPENAI_API_KEY!, "api.openai.com")
.create();
import os
from microsandbox import Sandbox, Secret
sb = await Sandbox.create(
"agent",
image="python",
secrets=[
Secret.env(
"GITHUB_TOKEN",
value=os.environ["GITHUB_TOKEN"],
allow_hosts=["api.github.com"],
allow_host_patterns=["*.githubusercontent.com"],
),
Secret.env(
"OPENAI_API_KEY",
value=os.environ["OPENAI_API_KEY"],
allow_hosts=["api.openai.com"],
),
],
)
sb, err := m.CreateSandbox(ctx, "agent",
m.WithImage("python"),
m.WithSecrets(
m.Secret.Env("GITHUB_TOKEN", os.Getenv("GITHUB_TOKEN"),
m.SecretEnvOptions{
AllowHosts: []string{"api.github.com"},
AllowHostPatterns: []string{"*.githubusercontent.com"},
},
),
m.Secret.Env("OPENAI_API_KEY", os.Getenv("OPENAI_API_KEY"),
m.SecretEnvOptions{AllowHosts: []string{"api.openai.com"}},
),
),
)
msb create python --name agent \
--secret "[email protected]" \
--secret "[email protected]"
In the CLI form, ENV@HOST reads the real value from the same-named host environment variable. Use ENV=VALUE@HOST when you need to provide an explicit value, such as [email protected]; shell-expanded values are passed to msb as command arguments.
See the SDK Reference for the full API: Rust | TypeScript | Python | Go.