---
title: Secrets
description: Let sandboxed code use credentials without receiving their values
keywords: ["sandbox secrets", "credential injection", "environment variables"]
icon: "key"
---

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
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
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
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
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
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
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
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
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
output = await sb.exec("sh", ["-c",
    'curl -sS https://api.github.com/user -H "Authorization: Bearer $GITHUB_TOKEN"',
])
print(output.stdout_text)
```

```go Go
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
output = sb.exec("sh", ["-c",
  'curl -sS https://api.github.com/user -H "Authorization: Bearer $GITHUB_TOKEN"'
])
puts output.stdout
```

```bash CLI
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.

## Choose substitution locations

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

Enable only the locations your API needs. For headers, list credential fields such as `Authorization`; an empty list permits every header and could let an allowed endpoint echo a substituted secret back to the guest.

### Query parameters

Enable query substitution only when the API requires a credential in the URL:

<CodeGroup>
```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!)
            .allow("api.github.com")
            .substituteInQuery(true)
    )
    .create();
```

```rust Rust
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
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
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
msb create python --name worker \
  --secret 'GITHUB_TOKEN:query@api.github.com'
```
</CodeGroup>

### Request bodies

Enable body substitution only when the API requires a credential in the body:

<CodeGroup>
```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!)
            .allow("api.github.com")
            .substituteInBody(true)
    )
    .create();
```

```rust Rust
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
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
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
msb create python --name worker \
  --secret 'GITHUB_TOKEN:body@api.github.com'
```
</CodeGroup>

<Accordion title="Substitution limits and disabled locations">

On a destination that passes the secret's host and TLS checks, placeholders in disabled locations pass through unchanged. Other destinations need explicit passthrough permission or follow the violation action. Disabling headers also disables Basic authentication substitution; a non-empty header list requires header substitution to be enabled.

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 actions

A secret-policy violation occurs when a request carries a placeholder to a destination that can receive neither the credential nor the unchanged placeholder. Choose what happens to that request:

| Action | Result |
| --- | --- |
| Block | Block without a violation log |
| Block and log (default) | Block the request and log a warning |
| Block and terminate | Block, log an error, and stop the sandbox |

Per-secret actions override the sandbox default. [Placeholder passthrough](#passthrough) is a separate host rule. The [denial message](#http-denial-responses) controls what a blocked HTTP request can show to the caller.

New local sandboxes return JSON `403` errors for supported secret-policy violations by default. Set `network.http.deny_response: false` to disable them. Customize the message with `network.http.secret_deny_message`. See [HTTP denial responses](/networking/overview#http-denial-responses) for the response format and limits.

### Block

Reject the request without a violation log:

<CodeGroup>
```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!)
            .allow("api.github.com")
            .violationAction("block")
    )
    .create();
```

```rust Rust
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
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
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
msb create python --name worker \
  --secret 'GITHUB_TOKEN@api.github.com' \
  --secret-violation-action block
```
</CodeGroup>

### Block and log

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

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

```rust Rust
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
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
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
msb create python --name worker \
  --secret 'GITHUB_TOKEN@api.github.com' \
  --secret-violation-action block-and-log
```
</CodeGroup>

### Block and terminate

Stop the sandbox when a violation occurs:

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

```rust Rust
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
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
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
msb create python --name worker \
  --secret 'GITHUB_TOKEN@api.github.com' \
  --secret-violation-action block-and-terminate
```
</CodeGroup>

<span id="passthrough" />

## Pass through 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
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
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
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
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
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="http-denial-responses" />

## Custom denial message

<Tooltip tip="JSON secret-denial responses were added after v0.7.8 and require a supporting local runtime. microsandbox cloud does not apply this setting."><span className="msb-badge-local">Local-only <Icon icon="circle-info" size={11} /></span></Tooltip>

Supported secret-policy blocks return JSON `403` responses by default. Set the message shown to the caller:

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

await using sb = await Sandbox.builder("worker")
    .image("python:3.12-bookworm")
    .network(n => n.http(h => h
        .denyResponse(true)
        .denyResponseFormat("json")
        .secretDenyMessage("Check secret access for this destination.")))
    .secret(s => s.env("GITHUB_TOKEN")
        .value(process.env.GITHUB_TOKEN!)
        .allow("api.github.com"))
    .create();
```

```rust Rust
use microsandbox::{Sandbox, sandbox::HttpDenyResponseFormat};

let token = std::env::var("GITHUB_TOKEN")?;
let sb = Sandbox::builder("worker")
    .image("python:3.12-bookworm")
    .network(|n| n.http(|h| h
        .deny_response(true)
        .deny_response_format(HttpDenyResponseFormat::Json)
        .secret_deny_message("Check secret access for this destination.")))
    .secret(|s| s.env("GITHUB_TOKEN")
        .value(token)
        .allow("api.github.com"))
    .create()
    .await?;
```

```python Python
import os
from microsandbox import HttpConfig, Network, Sandbox, Secret

sb = await Sandbox.create(
    "worker",
    image="python:3.12-bookworm",
    network=Network(http=HttpConfig(
        deny_response=True,
        deny_response_format="json",
        secret_deny_message="Check secret access for this destination.",
    )),
    secrets=[Secret.env(
        "GITHUB_TOKEN",
        value=os.environ["GITHUB_TOKEN"],
        allow=["api.github.com"],
    )],
)
```

```go Go
sb, err := m.CreateSandbox(ctx, "worker",
    m.WithImage("python:3.12-bookworm"),
    m.WithNetwork(&m.NetworkConfig{
        HTTP: &m.HTTPConfig{
            DenyResponse: true,
            DenyResponseFormat: m.HTTPDenyResponseJSON,
            SecretDenyMessage: "Check secret access for this destination.",
        },
    }),
    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
require "microsandbox"

sb = Microsandbox::Sandbox.create(
  "worker",
  image: "python:3.12-bookworm",
  http: {
    deny_response: true,
    deny_response_format: "json",
    secret_deny_message: "Check secret access for this destination."
  },
  secrets: [{
    env: "GITHUB_TOKEN",
    value: ENV.fetch("GITHUB_TOKEN"),
    allowed_host: "api.github.com"
  }]
)
```
</CodeGroup>

The message is literal; the response also includes `code: secret_policy_denied` and the requested `domain`. The [YAML example](#yaml-configuration) shows the same setting. See [HTTP denial responses](/networking/overview#http-denial-responses) for response limits, TLS requirements, and opt-out settings.

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

## Update secrets

<Tooltip tip="On microsandbox cloud, modify changes secrets only: a value, allowed hosts and removal can apply live; other secret changes need the next-start policy."><span className="msb-badge-limited">Limited on cloud <Icon icon="circle-info" size={11} /></span></Tooltip>

A secret's value, allowed hosts, and removal can apply live when supported. Adding a secret or changing its placeholder, substitution, violation action, TLS identity requirement, or placeholder hosts requires a restart. These examples use a host environment reference and allow a restart if needed:

<Accordion title="TLS and restart details">

On a local sandbox, new secrets require TLS identity by default and enable interception if needed. That change requires a restart. Existing secrets that explicitly allow plain-HTTP substitution can still rotate live. Removing the last TLS-dependent secret leaves interception enabled.

</Accordion>

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

// Substitute only in the Authorization header
await sb.modify({
  secrets: {
    GITHUB_TOKEN: { substitution: { headerFields: ["authorization"] } },
  },
  policy: "restart",
});

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

```rust Rust
use microsandbox::sandbox::{SecretSource, SecretSubstitution};

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

// Substitute only in the Authorization header
sb.modify()
    .secret(|s| s.env("GITHUB_TOKEN").substitution(SecretSubstitution {
        header_fields: vec!["authorization".into()],
        ..SecretSubstitution::default()
    }))
    .restart()
    .apply()
    .await?;

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

```python Python
from microsandbox import ModificationPolicy, SecretSubstitution

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

# Substitute only in the Authorization header
await sb.modify(
    secrets={
        "GITHUB_TOKEN": {
            "substitution": SecretSubstitution(header_fields=("authorization",)),
        },
    },
    policy=ModificationPolicy.RESTART,
)

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

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

// Substitute only in the Authorization header
_, err = sb.Modify(ctx, m.ModifyOptions{
    Secrets: map[string]m.SecretModifySpec{
        "GITHUB_TOKEN": {
            Substitution: &m.SecretSubstitution{HeaderFields: []string{"authorization"}},
        },
    },
    Policy: m.ModificationPolicyRestart,
})

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

```bash CLI
msb modify worker --secret GITHUB_TOKEN@api.github.com --restart  # add or rotate
msb modify worker --secret GITHUB_TOKEN:headers=authorization@api.github.com --restart  # Authorization header only
msb modify worker --secret-rm GITHUB_TOKEN                       # remove
```
</CodeGroup>

On microsandbox cloud, add a secret with the next-start policy (`--next-start`) instead of restart, then stop and start the sandbox.

Updating a sandbox secret does not rotate or revoke it at the issuing service. See [Live Modify](/sandboxes/tuning) for restart and TLS behavior.

## YAML configuration

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

<CodeGroup>
```yaml YAML
network:
  http:
    deny_response: true
    deny_response_format: json
    secret_deny_message: "Check secret access for this destination."
secrets:
  GITHUB_TOKEN:
    value: ${GITHUB_TOKEN}
    allow: [api.github.com]
    substitution:
      headers: true
      header_fields: []
      query: false
      body: false
    passthrough: [api.anthropic.com]
    violation_action: block-and-log
```
</CodeGroup>

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

## SDK reference

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