Back to Microsandbox

Secrets

docs/sandboxes/secrets.mdx

0.6.44.0 KB
Original Source

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.

<CodeGroup> ```rust Rust 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?;


```typescript TypeScript
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();
python
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"],
        ),
    ],
)
go
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"}},
        ),
    ),
)
bash
msb create python --name worker \
  --secret "[email protected]" \
  --secret "[email protected]"
</CodeGroup>

In the CLI form, ENV@HOST records a host-side source reference: the real value is read from the same-named host environment variable when the sandbox starts, and never lands in the durable config. The inline ENV=VALUE@HOST form is rejected on both msb create and msb modify — shell history and process listings would leak the value regardless — so providing a raw value is SDK-only.

<Warning> **Raw values persist at rest.** SDK paths that take a raw value — `.value(..)` in the secret closure, `secret_env()`, and modify's value-based rotate — store the value verbatim in the durable sandbox config. It stays there until a later modify rotates the secret to a source reference. Downstream behavior is identical to the reference path (placeholder-only guest, proxy injection, zeroized memory copies); only the at-rest property differs. A future host-side secret store will make these same calls import the value and store a reference, with no signature change. Prefer source references when the value can be referenced. </Warning>

For API details, see the SDK references: Rust | TypeScript | Python | Go.