> ## Documentation Index
> Fetch the complete documentation index at: https://docs.microsandbox.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Secrets

> Let sandboxed code use credentials without receiving their values

Secrets give sandboxed code a placeholder instead of a credential. When it sends that placeholder to an allowed API, microsandbox verifies the destination and substitutes the real value outside the guest.

## Add a secret

Set `GITHUB_TOKEN` in your host environment, then bind it to `api.github.com`:

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

  await using sb = await Sandbox.builder("worker")
      .image("python:3.12-bookworm")
      .secret(s =>
          s.env("GITHUB_TOKEN")
              .value(process.env.GITHUB_TOKEN!)
              .allow("api.github.com")
      )
      .create();
  ```

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

  let token = std::env::var("GITHUB_TOKEN")?;
  let sb = Sandbox::builder("worker")
      .image("python:3.12-bookworm")
      .secret(|s| s
          .env("GITHUB_TOKEN")
          .value(token)
          .allow("api.github.com")
      )
      .create()
      .await?;
  ```

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

  sb = await Sandbox.create(
      "worker",
      image="python:3.12-bookworm",
      secrets=[
          Secret.env(
              "GITHUB_TOKEN",
              value=os.environ["GITHUB_TOKEN"],
              allow=["api.github.com"],
          ),
      ],
  )
  ```

  ```go Go theme={null}
  sb, err := m.CreateSandbox(ctx, "worker",
      m.WithImage("python:3.12-bookworm"),
      m.WithSecrets(
          m.Secret.Env("GITHUB_TOKEN", os.Getenv("GITHUB_TOKEN"),
              m.SecretEnvOptions{
                  Allow: []string{"api.github.com"},
              },
          ),
      ),
  )
  if err != nil { return err }
  ```

  ```ruby Ruby theme={null}
  require "microsandbox"

  sb = Microsandbox::Sandbox.create(
    "worker",
    image: "python:3.12-bookworm",
    secrets: [{
      env: "GITHUB_TOKEN",
      value: ENV.fetch("GITHUB_TOKEN"),
      allowed_host: "api.github.com"
    }]
  )
  ```

  ```bash CLI theme={null}
  msb create python:3.12-bookworm --name worker \
    --secret 'GITHUB_TOKEN@api.github.com'
  ```
</CodeGroup>

The guest receives `$MSB_GITHUB_TOKEN`. Default placeholders preserve the environment variable’s spelling; custom placeholders are optional.

<Note>
  Passing a raw value through an SDK persists it in the host-side sandbox configuration. Stopping the sandbox does not remove that stored value. The CLI example stores an environment reference and reads it at sandbox startup; inline credential values are rejected.
</Note>

## Use a secret

Run the request inside the sandbox:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const output = await sb.exec("sh", ["-c",
      'curl -sS https://api.github.com/user -H "Authorization: Bearer $GITHUB_TOKEN"',
  ]);
  console.log(output.stdout());
  ```

  ```rust Rust theme={null}
  let output = sb.exec("sh", ["-c",
      r#"curl -sS https://api.github.com/user -H "Authorization: Bearer $GITHUB_TOKEN""#,
  ]).await?;
  println!("{}", output.stdout()?);
  ```

  ```python Python theme={null}
  output = await sb.exec("sh", ["-c",
      'curl -sS https://api.github.com/user -H "Authorization: Bearer $GITHUB_TOKEN"',
  ])
  print(output.stdout_text)
  ```

  ```go Go theme={null}
  output, err := sb.Exec(ctx, "sh", []string{"-c",
      `curl -sS https://api.github.com/user -H "Authorization: Bearer $GITHUB_TOKEN"`,
  })
  if err != nil { return err }
  fmt.Print(output.Stdout())
  ```

  ```ruby Ruby theme={null}
  output = sb.exec("sh", ["-c",
    'curl -sS https://api.github.com/user -H "Authorization: Bearer $GITHUB_TOKEN"'
  ])
  puts output.stdout
  ```

  ```bash CLI theme={null}
  msb exec worker -- sh -c \
    'curl -sS https://api.github.com/user -H "Authorization: Bearer $GITHUB_TOKEN"'
  ```
</CodeGroup>

Inside the sandbox, `GITHUB_TOKEN` holds a placeholder. microsandbox substitutes the real token when the request reaches the allowed host.

## Request locations

| Location | Default |
| - | - |
| Headers, including Basic authentication | Enabled |
| URL query parameters | Disabled |
| Request body | Disabled |

Enable only the locations your API needs. Disabling headers also disables Basic authentication substitution. Once the destination passes the secret's host and TLS identity checks, placeholders in disabled locations are forwarded unchanged. For example, a request can authenticate with a substituted header while keeping the placeholder in its body. Other destinations need explicit passthrough permission or follow the violation policy.

<Accordion title="Enable query substitution">
  Enable query substitution only when the API requires a credential in the URL:

  <CodeGroup>
    ```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")
                .substituteInQuery(true)
        )
        .create();
    ```

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

    let token = std::env::var("GITHUB_TOKEN")?;
    let sb = Sandbox::builder("worker")
        .image("python")
        .secret(|s| s
            .env("GITHUB_TOKEN")
            .value(token)
            .allow("api.github.com")
            .substitute_in_query(true)
        )
        .create()
        .await?;
    ```

    ```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"],
                substitution=SecretSubstitution(query=True),
            ),
        ],
    )
    ```

    ```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"},
                    Substitution: m.SecretSubstitution{Query: true},
                },
            ),
        ),
    )
    if err != nil { return err }
    ```

    ```bash CLI theme={null}
    msb create python --name worker \
      --secret 'GITHUB_TOKEN:query@api.github.com'
    ```
  </CodeGroup>
</Accordion>

<Accordion title="Enable body substitution">
  Enable body substitution only when the API requires a credential in the body:

  <CodeGroup>
    ```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")
                .substituteInBody(true)
        )
        .create();
    ```

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

    let token = std::env::var("GITHUB_TOKEN")?;
    let sb = Sandbox::builder("worker")
        .image("python")
        .secret(|s| s
            .env("GITHUB_TOKEN")
            .value(token)
            .allow("api.github.com")
            .substitute_in_body(true)
        )
        .create()
        .await?;
    ```

    ```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"],
                substitution=SecretSubstitution(body=True),
            ),
        ],
    )
    ```

    ```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"},
                    Substitution: m.SecretSubstitution{Body: true},
                },
            ),
        ),
    )
    if err != nil { return err }
    ```

    ```bash CLI theme={null}
    msb create python --name worker \
      --secret 'GITHUB_TOKEN:body@api.github.com'
    ```
  </CodeGroup>

  When enabled, body substitution supports fixed-length HTTP/1 bodies up to 16 MiB and chunked HTTP/1 bodies. Larger fixed-length bodies are blocked. Encoded bodies pass through unchanged; HTTP/2 body substitution is unsupported and matching body placeholders are blocked. See [body limits](/sdk/typescript/secrets#secret-substituteinbody).
</Accordion>

## Violation policy

When a destination is not permitted to receive the credential or the unchanged placeholder, these policies control what happens to the request:

| Policy | Result |
| - | - |
| Block and log (default) | Block the request and log a warning |
| Block | Block without a violation log |
| Block and terminate | Block, log an error, and stop the sandbox |
| Passthrough | Let selected hosts receive the unchanged placeholder |

The first three are violation actions. Passthrough is a separate per-secret host rule; unmatched requests still follow the violation action.

### Block and log

The default. No additional configuration is needed; to set it explicitly:

<CodeGroup>
  ```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")
              .violationAction("block-and-log")
      )
      .create();
  ```

  ```rust Rust theme={null}
  use microsandbox::{Sandbox, SecretViolationAction};

  let token = std::env::var("GITHUB_TOKEN")?;
  let sb = Sandbox::builder("worker")
      .image("python")
      .secret(|s| s
          .env("GITHUB_TOKEN")
          .value(token)
          .allow("api.github.com")
          .violation_action(SecretViolationAction::BlockAndLog)
      )
      .create()
      .await?;
  ```

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

  sb = await Sandbox.create(
      "worker",
      image="python",
      secrets=[
          Secret.env(
              "GITHUB_TOKEN",
              value=os.environ["GITHUB_TOKEN"],
              allow=["api.github.com"],
              violation_action=ViolationAction.BLOCK_AND_LOG,
          ),
      ],
  )
  ```

  ```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"},
                  ViolationAction: m.ViolationActionBlockAndLog,
              },
          ),
      ),
  )
  if err != nil { return err }
  ```

  ```bash CLI theme={null}
  msb create python --name worker \
    --secret 'GITHUB_TOKEN@api.github.com' \
    --secret-violation-action block-and-log
  ```
</CodeGroup>

### Block

Reject the request without a violation log:

<CodeGroup>
  ```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")
              .violationAction("block")
      )
      .create();
  ```

  ```rust Rust theme={null}
  use microsandbox::{Sandbox, SecretViolationAction};

  let token = std::env::var("GITHUB_TOKEN")?;
  let sb = Sandbox::builder("worker")
      .image("python")
      .secret(|s| s
          .env("GITHUB_TOKEN")
          .value(token)
          .allow("api.github.com")
          .violation_action(SecretViolationAction::Block)
      )
      .create()
      .await?;
  ```

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

  sb = await Sandbox.create(
      "worker",
      image="python",
      secrets=[
          Secret.env(
              "GITHUB_TOKEN",
              value=os.environ["GITHUB_TOKEN"],
              allow=["api.github.com"],
              violation_action=ViolationAction.BLOCK,
          ),
      ],
  )
  ```

  ```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"},
                  ViolationAction: m.ViolationActionBlock,
              },
          ),
      ),
  )
  if err != nil { return err }
  ```

  ```bash CLI theme={null}
  msb create python --name worker \
    --secret 'GITHUB_TOKEN@api.github.com' \
    --secret-violation-action block
  ```
</CodeGroup>

### Block and terminate

Stop the sandbox when a violation occurs:

<CodeGroup>
  ```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")
              .violationAction("block-and-terminate")
      )
      .create();
  ```

  ```rust Rust theme={null}
  use microsandbox::{Sandbox, SecretViolationAction};

  let token = std::env::var("GITHUB_TOKEN")?;
  let sb = Sandbox::builder("worker")
      .image("python")
      .secret(|s| s
          .env("GITHUB_TOKEN")
          .value(token)
          .allow("api.github.com")
          .violation_action(SecretViolationAction::BlockAndTerminate)
      )
      .create()
      .await?;
  ```

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

  sb = await Sandbox.create(
      "worker",
      image="python",
      secrets=[
          Secret.env(
              "GITHUB_TOKEN",
              value=os.environ["GITHUB_TOKEN"],
              allow=["api.github.com"],
              violation_action=ViolationAction.BLOCK_AND_TERMINATE,
          ),
      ],
  )
  ```

  ```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"},
                  ViolationAction: m.ViolationActionBlockAndTerminate,
              },
          ),
      ),
  )
  if err != nil { return err }
  ```

  ```bash CLI theme={null}
  msb create python --name worker \
    --secret 'GITHUB_TOKEN@api.github.com' \
    --secret-violation-action block-and-terminate
  ```
</CodeGroup>

The SDK examples set a per-secret action; the CLI flag sets the sandbox-wide default. An explicit per-secret action takes precedence.

<span id="passthrough" />

### Allow placeholders

Allow an additional host to receive the **unchanged placeholder** where substitution does not apply. Credential-allowed hosts already receive unchanged placeholders in disabled locations after passing the secret's identity checks. This permission does not grant access to the credential; existing substitution permissions still apply. For example, allow the placeholder to appear in an AI request:

<CodeGroup>
  ```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")
              .allowPlaceholderFor("api.anthropic.com")
      )
      .create();
  ```

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

  let token = std::env::var("GITHUB_TOKEN")?;
  let sb = Sandbox::builder("worker")
      .image("python")
      .secret(|s| s
          .env("GITHUB_TOKEN")
          .value(token)
          .allow("api.github.com")
          .allow_placeholder_for("api.anthropic.com")
      )
      .create()
      .await?;
  ```

  ```python Python theme={null}
  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=["api.github.com"],
              allow_placeholder_for=["api.anthropic.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"},
                  AllowPlaceholderFor: []string{"api.anthropic.com"},
              },
          ),
      ),
  )
  if err != nil { return err }
  ```

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

Passthrough neither grants network access nor adds the host to the credential allow list. Other destinations still follow the violation action.

<span id="change-while-running" />

## Update secrets

<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>

Existing values and removals can apply live when supported. Adding a secret or changing its placeholder requires a restart. These examples use a host environment reference and allow a restart if needed:

New secrets added through `modify` require TLS identity by default and turn interception on when it is off, the same way `secret` does at create time. Interception cannot start on a running sandbox, so that change is restart-backed and appears in the plan as `tls`. Existing secrets that explicitly allow plain-HTTP substitution with `require_tls_identity(false)` continue to rotate live without enabling interception. Removing every TLS-dependent secret leaves interception on, since it may have been enabled for reasons of its own.

<CodeGroup>
  ```typescript TypeScript theme={null}
  await sb.modify({
    secrets: {
      GITHUB_TOKEN: { env: "GITHUB_TOKEN", allowedHosts: ["api.github.com"] },
    },
    policy: "restart",
  });

  await sb.modify({ secretsRemove: ["GITHUB_TOKEN"] });
  ```

  ```rust Rust theme={null}
  use microsandbox::sandbox::SecretSource;

  let plan = sb.modify()
      .secret(|s| s
          .env("GITHUB_TOKEN")
          .source(SecretSource::Env { var: "GITHUB_TOKEN".into() })
          .allow("api.github.com"))
      .restart()
      .apply()
      .await?;

  sb.modify().remove_secret("GITHUB_TOKEN").apply().await?;
  ```

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

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

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

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

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

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

Updating a sandbox secret does not rotate or revoke it at the issuing service. Do that separately. See [Live Modify](/sandboxes/tuning) for restart policies.

## YAML configuration

You can also declare secrets in sandbox YAML. An exact environment reference avoids persisting the resolved value:

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

In YAML, `substitution` selects where credentials are inserted; `passthrough` permits unchanged placeholders on additional hosts. A top-level `secret_violation_action` sets the sandbox-wide default. See [CLI configuration](/cli/configuration#secrets) for loading configuration and CLI equivalents.

## Security boundary

* **Destination checks:** Injection checks the allowed host against observed DNS and TLS identity, including the HTTP request authority. A forged hostname or hard-coded IP is not enough.
* **TLS:** Injection requires intercepted TLS by default. Bypassed TLS cannot be inspected for placeholders or receive injected credentials. See [TLS inspection](/networking/tls).
* **Trusted hosts:** Allowed endpoints receive the real value and could return it to the guest. Keep allow lists narrow and include redirect destinations only when trusted. Wildcards include the root domain and its subdomains.
* **Network access:** Secret rules decide where credentials can be injected; network rules still control connectivity.
* **Guest-side signing:** A placeholder cannot replace raw credentials used to sign requests inside the guest. Supplying the real value as a plain environment variable exposes it to guest code.

See [Secret handling](/security/secrets) for the protection boundary and storage details.

## Troubleshooting

| Symptom | Check |
| - | - |
| Request blocked | Check host logs, destination rules, and enabled request locations. |
| Sandbox stops after a request | Check for a terminate-on-violation policy. |
| API receives a placeholder | Check TLS interception, substitution settings, and passthrough rules. |
| Guest prints the placeholder | Expected: substitution happens outside the guest. |

## Reference

[TypeScript](/sdk/typescript/secrets) · [Rust](/sdk/rust/secrets) · [Python](/sdk/python/secrets) · [Go](/sdk/go/secrets)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.