Skip to main content
Secrets keep credentials on the host while giving sandboxed code a placeholder to use. When you bind a secret to an environment variable, microsandbox puts a placeholder in the guest instead of the real value. By default that placeholder is $MSB_<env_var>, using the environment variable name exactly as provided, and you can provide a custom placeholder when needed. If the sandbox sends the placeholder to an allowed host, microsandbox swaps it for the real credential at the network boundary. Anywhere else, the placeholder remains meaningless. That means the guest can call APIs without ever holding the credential itself. Allowed hosts are checked against the sandbox’s observed DNS and TLS identity. Keep allow lists narrow so placeholders can only turn into credentials at the destinations that actually need them.

Add at create time

Bind secrets to environment variables when you create the sandbox, each scoped to the hosts allowed to receive it:
use microsandbox::Sandbox;

let sb = Sandbox::builder("worker")
    .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("SERVICE_API_KEY", service_api_key, "api.example.com")
    .create()
    .await?;
import { Sandbox } from "microsandbox";

await using sb = await Sandbox.builder("worker")
    .image("python")
    .secret((s) =>
        s.env("GITHUB_TOKEN")
            .value(process.env.GITHUB_TOKEN!)
            .allowHost("api.github.com")
            .allowHostPattern("*.githubusercontent.com"),
    )
    .secretEnv("SERVICE_API_KEY", process.env.SERVICE_API_KEY!, "api.example.com")
    .create();
import os
from microsandbox import Sandbox, Secret

sb = await Sandbox.create(
    "worker",
    image="python",
    secrets=[
        Secret.env(
            "GITHUB_TOKEN",
            value=os.environ["GITHUB_TOKEN"],
            allow_hosts=["api.github.com"],
            allow_host_patterns=["*.githubusercontent.com"],
        ),
        Secret.env(
            "SERVICE_API_KEY",
            value=os.environ["SERVICE_API_KEY"],
            allow_hosts=["api.example.com"],
        ),
    ],
)
sb, err := m.CreateSandbox(ctx, "worker",
    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("SERVICE_API_KEY", os.Getenv("SERVICE_API_KEY"),
            m.SecretEnvOptions{AllowHosts: []string{"api.example.com"}},
        ),
    ),
)
msb create python --name worker \
  --secret "GITHUB_TOKEN@api.github.com" \
  --secret "SERVICE_API_KEY@api.example.com"
In the CLI form, ENV@HOST stores a reference, not the value. Each time the sandbox starts, the value is read fresh from the host environment variable with the same name, so it never ends up in the sandbox config file. The CLI does not accept inline values (ENV=VALUE@HOST is rejected by both msb create and msb modify), because a value typed into a command line leaks into shell history and process listings. To pass a raw value, use an SDK.
Raw values are saved to disk. When you pass a raw value through an SDK (.value(..), secret_env(), or a value-based rotate), it is stored as-is in the sandbox config file and stays there until you rotate the secret to a reference. The sandbox behaves the same either way; the only difference is what ends up on disk. Prefer references whenever the value is available in a host environment variable.

Change while running

Add, rotate, or remove secrets without a restart. Guest code keeps using the same placeholder; only the value injected at the network boundary changes.
let plan = sb.modify()
    .secret(|s| s
        .env("API_KEY")
        .source(SecretSource::Env { var: "API_KEY".into() })
        .allow_host("api.example.com"))
    .apply()
    .await?;
msb modify worker --secret GITHUB_TOKEN@api.github.com   # add or rotate
msb modify worker --secret-rm SERVICE_API_KEY            # remove
Secret changes through modify are Rust-SDK and CLI only for now. See Tuning for how changes are planned and applied. For API details, see the SDK references: Rust | TypeScript | Python | Go.