> ## Documentation Index
> Fetch the complete documentation index at: https://microsanbox-staging-appcypher-sdk-runtime-bootstrap.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Secrets

> Secure credential injection for sandboxes

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 and enabled request location, microsandbox swaps it for the real credential at the network boundary. Elsewhere, microsandbox blocks the placeholder by default so it cannot reveal which credentials the workload expects. Use explicit passthrough hosts only when a destination must receive the unchanged placeholder.

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:

<CodeGroup>
  ```rust Rust theme={null}
  use microsandbox::Sandbox;

  let sb = Sandbox::builder("worker")
      .image("python")
      .secret(|s| s
          .env("GITHUB_TOKEN")
          .value(std::env::var("GITHUB_TOKEN")?)
          .allow("api.github.com")
          .allow("*.githubusercontent.com")
          .allow_passthrough_for("api.anthropic.com")
          .substitute_in_body(true)
      )
      .secret_env("SERVICE_API_KEY", service_api_key, "api.example.com")
      .create()
      .await?;
  ```

  ```typescript TypeScript theme={null}
  import { Sandbox } from "microsandbox";

  await using sb = await Sandbox.builder("worker")
      .image("python")
      .secret((s) =>
          s.env("GITHUB_TOKEN")
              .value(process.env.GITHUB_TOKEN!)
              .allow("api.github.com")
              .allow("*.githubusercontent.com")
              .allowPassthroughFor("api.anthropic.com")
              .substituteInBody(true),
      )
      .secretEnv("SERVICE_API_KEY", process.env.SERVICE_API_KEY!, "api.example.com")
      .create();
  ```

  ```python Python theme={null}
  import os
  from microsandbox import Sandbox, Secret, SecretSubstitution

  sb = await Sandbox.create(
      "worker",
      image="python",
      secrets=[
          Secret.env(
              "GITHUB_TOKEN",
              value=os.environ["GITHUB_TOKEN"],
              allow=["api.github.com", "*.githubusercontent.com"],
              passthrough=["api.anthropic.com"],
              substitution=SecretSubstitution(body=True),
          ),
          Secret.env(
              "SERVICE_API_KEY",
              value=os.environ["SERVICE_API_KEY"],
              allow=["api.example.com"],
          ),
      ],
  )
  ```

  ```go Go theme={null}
  sb, err := m.CreateSandbox(ctx, "worker",
      m.WithImage("python"),
      m.WithSecrets(
          m.Secret.Env("GITHUB_TOKEN", os.Getenv("GITHUB_TOKEN"),
              m.SecretEnvOptions{
                  Allow:       []string{"api.github.com", "*.githubusercontent.com"},
                  Passthrough: []string{"api.anthropic.com"},
                  Substitution: m.SecretSubstitution{Body: true},
              },
          ),
          m.Secret.Env("SERVICE_API_KEY", os.Getenv("SERVICE_API_KEY"),
              m.SecretEnvOptions{Allow: []string{"api.example.com"}},
          ),
      ),
  )
  ```

  ```bash CLI theme={null}
  msb create python --name worker \
    --secret 'GITHUB_TOKEN:body,passthrough=api.anthropic.com@api.github.com,*.githubusercontent.com' \
    --secret "SERVICE_API_KEY@api.example.com"
  ```
</CodeGroup>

In the CLI form, `ENV@HOST[,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.

Options follow the secret name after `:`. `body` enables body substitution; `no-headers`, `no-query`, and `no-body` disable locations; and `passthrough=HOST` permits that host to receive an unchanged placeholder. For multiple passthrough hosts, use `passthrough=[HOST,...]`, repeat the option, or repeat `--secret`; repeated values are merged.

```bash theme={null}
msb run \
  --secret 'GH_TOKEN:body,passthrough=api.anthropic.com,passthrough=[uploads.example.com,*.events.example.com]@github.com,api.github.com' \
  --secret-violation-action block-and-log \
  image
```

The sandbox-wide `--secret-violation-action` accepts `block`, `block-and-log`, or `block-and-terminate`. Passthrough is deliberately not a violation action because it is scoped per secret and per host.

## YAML configuration

YAML keeps substitution and passthrough as separate per-secret policies. Values can use the existing environment interpolation syntax:

```yaml theme={null}
secret_violation_action: block-and-log
secrets:
  GH_TOKEN:
    value: ${GH_TOKEN}
    allow:
      - github.com
      - api.github.com
    substitution:
      headers: false
      query: false
      body: true
    passthrough:
      - api.anthropic.com
    violation_action: block-and-terminate
    require_tls_identity: true
```

Setting `substitution.headers: false` disallows substitution in all request headers, including Basic authentication. A placeholder found there still blocks unless the destination matches `passthrough`.

<Warning>
  **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.
</Warning>

## Change while running

<Tooltip tip="modify is not yet available on microsandbox cloud; recreate the sandbox to change its secrets."><span className="msb-badge-local">Local-only <Icon icon="circle-info" size={11} /></span></Tooltip>
Rotate or remove existing secrets without a restart. Adding a secret or changing its guest-visible placeholder requires a restart so the new environment reaches the guest. Later rotations keep that placeholder stable and only change the value injected at the network boundary.

<CodeGroup>
  ```rust Rust theme={null}
  let plan = sb.modify()
      .secret(|s| s
          .env("API_KEY")
          .source(SecretSource::Env { var: "API_KEY".into() })
          .allow("api.example.com"))
      .restart()
      .apply()
      .await?;
  ```

  ```typescript TypeScript theme={null}
  await sandbox.modify({
    secrets: {
      API_KEY: { env: "API_KEY", allowedHosts: ["api.example.com"] },
    },
    policy: "restart",
  });

  await sandbox.modify({ secretsRemove: ["SERVICE_API_KEY"] });
  ```

  ```python Python theme={null}
  from microsandbox import ModificationPolicy

  await sb.modify(
      secrets={
          "API_KEY": {
              "env": "API_KEY",
              "allowed_hosts": ["api.example.com"],
          },
      },
      policy=ModificationPolicy.RESTART,
  )

  await sb.modify(secrets_rm=["SERVICE_API_KEY"])
  ```

  ```go Go theme={null}
  _, err := sb.Modify(ctx, m.ModifyOptions{
      Secrets: map[string]m.SecretModifySpec{
          "API_KEY": {
              Env:          "API_KEY",
              AllowedHosts: []string{"api.example.com"},
          },
      },
      Policy: m.ModificationPolicyRestart,
  })

  _, err = sb.Modify(ctx, m.ModifyOptions{
      SecretsRemove: []string{"SERVICE_API_KEY"},
  })
  ```

  ```bash CLI theme={null}
  msb modify worker --secret GITHUB_TOKEN@api.github.com --restart  # add or rotate
  msb modify worker --secret-rm SERVICE_API_KEY                       # remove
  ```
</CodeGroup>

Secret modification is available through every SDK and the CLI. See [Tuning](/sandboxes/tuning) for how changes are planned and applied.

For API details, see the SDK references: [Rust](/sdk/rust/secrets) | [TypeScript](/sdk/typescript/secrets) | [Python](/sdk/python/secrets) | [Go](/sdk/go/secrets).
