Back to Microsandbox

Secrets

docs/sdk/rust/secrets.mdx

0.5.1026.3 KB
Original Source

Inject credentials into outbound HTTP without ever exposing the real value to the guest. The guest only ever sees a placeholder; microsandbox's TLS proxy swaps in the real secret on connections to allowed hosts and blocks everything else. See Secrets for how placeholder substitution works and usage examples.

<div className="msb-glance"> <p className="msb-gl"><span className="msb-dot static"></span>Static<span className="msb-ct">1</span></p> <a className="msb-row" href="#secretbuildernew"><span className="msb-rn">SecretBuilder::new()</span><span className="msb-rg">start a secret builder</span></a> <p className="msb-gl"><span className="msb-dot builder"></span>Builder · SecretBuilder<span className="msb-ct">13</span></p> <div className="msb-chiprow"> <a className="msb-chip" href="#env">.env()</a> <a className="msb-chip" href="#value">.value()</a> <a className="msb-chip" href="#allow_host">.allow_host()</a> <a className="msb-chip" href="#allow_host_pattern">.allow_host_pattern()</a> <a className="msb-chip" href="#allow_any_host_dangerous">.allow_any_host_dangerous()</a> <a className="msb-chip" href="#placeholder">.placeholder()</a> <a className="msb-chip" href="#on_violation">.on_violation()</a> <a className="msb-chip" href="#require_tls_identity">.require_tls_identity()</a> <a className="msb-chip" href="#inject_headers">.inject_headers()</a> <a className="msb-chip" href="#inject_basic_auth">.inject_basic_auth()</a> <a className="msb-chip" href="#inject_query">.inject_query()</a> <a className="msb-chip" href="#inject_body">.inject_body()</a> <a className="msb-chip" href="#build">.build()</a> </div> <p className="msb-gl"><span className="msb-dot builder"></span>Shorthand · SandboxBuilder<span className="msb-ct">2</span></p> <a className="msb-row" href="#secret_env"><span className="msb-rn">.secret_env()</span><span className="msb-rg">one-line header-injected secret</span></a> <a className="msb-row" href="#secret_entry"><span className="msb-rn">.secret_entry()</span><span className="msb-rg">add a prebuilt SecretEntry</span></a> <p className="msb-gl"><span className="msb-dot type"></span>Types</p> <div className="msb-chiprow"> <a className="msb-typepill" href="#hostpattern">HostPattern</a> <a className="msb-typepill" href="#secretentry">SecretEntry</a> <a className="msb-typepill" href="#secretinjection">SecretInjection</a> <a className="msb-typepill" href="#violationaction">ViolationAction</a> <a className="msb-typepill" href="#secretconfigerror">SecretConfigError</a> <a className="msb-typepill" href="/sdk/rust/networking#violationactionbuilder">ViolationActionBuilder</a> </div> </div> <p className="msb-label" id="typical-flow">Typical flow</p>
rust
use microsandbox::Sandbox;

let sb = Sandbox::builder("agent")
    .image("python")
    .secret(|s| s
        .env("OPENAI_API_KEY")             // guest sees $MSB_OPENAI_API_KEY
        .value(std::env::var("OPENAI_API_KEY")?)
        .allow_host("api.openai.com"))     // real value only reaches this host
    .create()
    .await?;

// In the guest, the placeholder is swapped for the real key
// only on TLS connections to api.openai.com.
let out = sb.exec("python", ["agent.py"]).await?;

Static methods


<span className="msb-recv">SecretBuilder::</span><span className="msb-hn">new()</span>

<div className="msb-tags"><span className="msb-tag is-static">static</span></div>
rust
fn new() -> SecretBuilder

Start building a secret with defaults: no env var, no value, no allowed hosts, the default SecretInjection (headers and Basic Auth on, query and body off), no per-secret violation override, and require_tls_identity = true. You rarely call this yourself: SandboxBuilder::secret(|s| s...) hands you a fresh builder and calls build() for you. SecretBuilder::default() is equivalent.

<p className="msb-label">Returns</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><a className="msb-type" href="#secretbuilder">SecretBuilder</a></div> <div className="msb-param-desc">A builder with default injection scopes.</div> </div> </div> <Accordion title="Example">
rust
use microsandbox::sandbox::SecretBuilder;

let entry = SecretBuilder::new()
    .env("STRIPE_KEY")
    .value(stripe_key)
    .allow_host("api.stripe.com")
    .build();
</Accordion>

SecretBuilder

Builder for one secret's placeholder, allowed hosts, and injection scopes. Obtained through SandboxBuilder::secret(|s| s...) or SecretBuilder::new(); each secret maps an environment variable to a real value that is only revealed when traffic reaches an allowed host through the TLS proxy. Every setter returns Self, so calls chain. env(), value(), and at least one allowed host are required; build() panics otherwise.

Import path: microsandbox::sandbox::SecretBuilder. Adding any secret automatically enables TLS interception.


<span className="msb-recv">.</span><span className="msb-hn">env()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn env(self, var: impl Into<String>) -> Self

Set the environment variable name that holds the placeholder inside the guest. The guest sees $MSB_<var> (or a custom placeholder), never the real value. Names must be non-empty and cannot contain = or NUL; shell-identifier syntax is not required since Linux only requires a NAME=value shape. Required.

<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>var</code><span className="msb-type">impl Into&lt;String&gt;</span></div> <div className="msb-param-desc">Environment variable name (non-empty, no <code>=</code> or NUL).</div> </div> </div>

<span className="msb-recv">.</span><span className="msb-hn">value()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn value(self, value: impl Into<String>) -> Self

Set the real secret value. This is the string that replaces the placeholder when a request reaches an allowed host. It never enters the guest VM. Required.

<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>value</code><span className="msb-type">impl Into&lt;String&gt;</span></div> <div className="msb-param-desc">The actual credential or token.</div> </div> </div>

<span className="msb-recv">.</span><span className="msb-hn">placeholder()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn placeholder(self, placeholder: impl Into<String>) -> Self

Override the auto-generated placeholder string. By default microsandbox generates $MSB_<env_var>. Use this when you need a specific format or when the placeholder must match a particular byte length. Placeholders must be non-empty, at most 1024 bytes (MAX_SECRET_PLACEHOLDER_BYTES), and cannot contain NUL, CR, or LF.

<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>placeholder</code><span className="msb-type">impl Into&lt;String&gt;</span></div> <div className="msb-param-desc">Custom placeholder string: non-empty, up to 1024 bytes, no NUL/CR/LF.</div> </div> </div>

<span className="msb-recv">.</span><span className="msb-hn">allow_host()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn allow_host(self, host: impl Into<String>) -> Self

Add an exact host allowed to receive the real secret value. The proxy checks the host against the connection's verified TLS identity and observed DNS history. Can be called multiple times to allow several hosts. At least one allowed host (exact, pattern, or any) is required.

<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>host</code><span className="msb-type">impl Into&lt;String&gt;</span></div> <div className="msb-param-desc">Exact hostname, e.g. <code>"api.example.com"</code>. Becomes a <a className="msb-type" href="#hostpattern">HostPattern::Exact</a>.</div> </div> </div> <Accordion title="Example">
rust
.secret(|s| s
    .env("STRIPE_KEY")
    .value(stripe_key)
    .allow_host("api.stripe.com")
    .allow_host("files.stripe.com"))
</Accordion>

<span className="msb-recv">.</span><span className="msb-hn">allow_host_pattern()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn allow_host_pattern(self, pattern: impl Into<String>) -> Self

Add a wildcard host pattern. A *.suffix pattern matches the suffix itself and any single-or-multi label subdomain of it.

<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>pattern</code><span className="msb-type">impl Into&lt;String&gt;</span></div> <div className="msb-param-desc">Wildcard pattern, e.g. <code>"*.googleapis.com"</code>. Becomes a <a className="msb-type" href="#hostpattern">HostPattern::Wildcard</a>.</div> </div> </div>

<span className="msb-recv">.</span><span className="msb-hn">allow_any_host_dangerous()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn allow_any_host_dangerous(self, i_understand_the_risk: bool) -> Self

Allow substitution on any host. Every server the guest connects to can then receive the real secret, which effectively disables host-based protection. The call is a no-op unless i_understand_the_risk is true. Only use this when the secret is not sensitive or the sandbox network is fully locked down. Adds a HostPattern::Any to the allow list, the one pattern that skips DNS and TLS-identity pinning.

<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>i_understand_the_risk</code><span className="msb-type">bool</span></div> <div className="msb-param-desc">Must be <code>true</code> to take effect.</div> </div> </div>

<span className="msb-recv">.</span><span className="msb-hn">on_violation()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn on_violation(
    self,
    f: impl FnOnce(ViolationActionBuilder) -> ViolationActionBuilder,
) -> Self

Configure violation behavior for this secret. Overrides the sandbox-wide secret violation policy and can let selected hosts receive the placeholder unchanged. See ViolationActionBuilder for the full set of block_* and passthrough_* methods.

Passthrough hosts do not receive the real secret value; substitution still only happens for hosts configured with allow_host() or allow_host_pattern(). When a per-secret passthrough policy does not match the request host, microsandbox falls back to the sandbox-wide secret violation action.

<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>f</code><a className="msb-type" href="/sdk/rust/networking#violationactionbuilder">FnOnce(ViolationActionBuilder)</a></div> <div className="msb-param-desc">Configure the per-secret violation action.</div> </div> </div> <Accordion title="Example">
rust
.secret(|s| s
    .env("API_KEY")
    .value(api_key)
    .allow_host("api.github.com")
    .on_violation(|v| v
        .block_and_log()
        .passthrough_host("api.anthropic.com")))
</Accordion>

<span className="msb-recv">.</span><span className="msb-hn">require_tls_identity()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn require_tls_identity(self, enabled: bool) -> Self

When true, the secret is only substituted on TLS-intercepted connections where the proxy has verified it is performing MITM and the SNI matches an allowed host. Bypassed TLS is opaque and never receives substitution. Disable only when you know the traffic path is safe and explicitly supports non-TLS substitution. Default: true.

<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>enabled</code><span className="msb-type">bool</span></div> <div className="msb-param-desc">Require verified TLS identity. Default: <code>true</code>.</div> </div> </div>

<span className="msb-recv">.</span><span className="msb-hn">inject_headers()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn inject_headers(self, enabled: bool) -> Self

Control whether the placeholder is replaced anywhere in HTTP headers. This is the most common injection scope, covering Authorization: Bearer $MSB_... and similar patterns. Default: true.

<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>enabled</code><span className="msb-type">bool</span></div> <div className="msb-param-desc">Substitute in headers. Default: <code>true</code>.</div> </div> </div>

<span className="msb-recv">.</span><span className="msb-hn">inject_basic_auth()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn inject_basic_auth(self, enabled: bool) -> Self

Control whether Authorization: Basic <base64> credentials are decoded, substituted in the decoded user:password, then re-encoded. Orthogonal to inject_headers: this flag handles the encoded-credentials case for the Basic scheme; inject_headers handles literal substitution in any header line, including non-Basic Authorization schemes (Bearer, Digest). Default: true.

<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>enabled</code><span className="msb-type">bool</span></div> <div className="msb-param-desc">Substitute inside Basic Auth credentials. Default: <code>true</code>.</div> </div> </div>

<span className="msb-recv">.</span><span className="msb-hn">inject_query()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn inject_query(self, enabled: bool) -> Self

Control whether the placeholder is replaced in the URL query string (the ?key=value portion of the request line). Default: false.

<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>enabled</code><span className="msb-type">bool</span></div> <div className="msb-param-desc">Substitute in query parameters. Default: <code>false</code>.</div> </div> </div>

<span className="msb-recv">.</span><span className="msb-hn">inject_body()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn inject_body(self, enabled: bool) -> Self

Control whether the placeholder is replaced in HTTP/1 request bodies. Fixed-length bodies up to 16 MiB are substituted and their Content-Length updated; larger fixed-length bodies are blocked. Chunked bodies are decoded and re-encoded with fresh chunk sizes. Encoded bodies pass through unchanged. HTTP/2 DATA-frame substitution is not supported, so matching body placeholders are blocked rather than leaked. Default: false.

<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>enabled</code><span className="msb-type">bool</span></div> <div className="msb-param-desc">Substitute in request bodies. Default: <code>false</code>.</div> </div> </div>

<span className="msb-recv">.</span><span className="msb-hn">build()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn build(self) -> SecretEntry

Materialize the SecretEntry. Called for you by SandboxBuilder::secret, so you rarely call it directly. If placeholder was not set, it defaults to $MSB_<env_var>.

<p className="msb-label">Returns</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><a className="msb-type" href="#secretentry">SecretEntry</a></div> <div className="msb-param-desc">The materialized secret entry.</div> </div> </div> <p className="msb-label">Panics</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-desc">Panics if <code>env</code> or <code>value</code> was not set, or if the allow list is empty. Use <a className="msb-type" href="#allow_any_host_dangerous">allow_any_host_dangerous(true)</a> for an explicit any-host secret.</div> </div> </div>

Shorthand

Two convenience methods on SandboxBuilder for the common cases, so you don't have to spell out a SecretBuilder closure. Both automatically enable TLS interception.


<span className="msb-recv">.</span><span className="msb-hn">secret_env()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn secret_env(
    self,
    env_var: impl Into<String>,
    value: impl Into<String>,
    allowed_host: impl Into<String>,
) -> Self

Equivalent to .secret(|s| s.env(env_var).value(value).allow_host(allowed_host)). The placeholder is auto-generated as $MSB_<env_var> and the default injection scopes apply (headers and Basic Auth enabled, query and body disabled).

<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>env_var</code><span className="msb-type">impl Into&lt;String&gt;</span></div> <div className="msb-param-desc">Environment variable name (non-empty, no <code>=</code> or NUL).</div> </div> <div className="msb-param"> <div className="msb-param-key"><code>value</code><span className="msb-type">impl Into&lt;String&gt;</span></div> <div className="msb-param-desc">Secret value.</div> </div> <div className="msb-param"> <div className="msb-param-key"><code>allowed_host</code><span className="msb-type">impl Into&lt;String&gt;</span></div> <div className="msb-param-desc">Allowed destination host (exact match).</div> </div> </div> <Accordion title="Example">
rust
let sb = Sandbox::builder("agent")
    .image("python")
    .secret_env("OPENAI_API_KEY", api_key, "api.openai.com")
    .create()
    .await?;
</Accordion>

<span className="msb-recv">.</span><span className="msb-hn">secret_entry()</span>

<div className="msb-tags"><span className="msb-tag is-builder">builder</span></div>
rust
fn secret_entry(self, entry: SecretEntry) -> Self

Add a pre-built SecretEntry directly. This is the escape hatch when you already have a materialized entry, for example one produced by SecretBuilder::build(), loaded from configuration, or constructed by hand for full control over the serialized shape. Both secret() and secret_env() funnel through this method. The entry is validated when the sandbox is built; an invalid one surfaces as a config error rather than a panic.

<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>entry</code><a className="msb-type" href="#secretentry">SecretEntry</a></div> <div className="msb-param-desc">A materialized secret entry.</div> </div> </div> <Accordion title="Example">
rust
use microsandbox::sandbox::SecretBuilder;

let entry = SecretBuilder::new()
    .env("STRIPE_KEY")
    .value(stripe_key)
    .allow_host("api.stripe.com")
    .build();

let sb = Sandbox::builder("billing")
    .image("python")
    .secret_entry(entry)
    .create()
    .await?;
</Accordion>

Types

HostPattern

<div className="msb-tags"><span className="msb-tag is-type">enum</span></div> <p className="msb-backref">used by <a href="#secretentry">SecretEntry</a> · <a href="#allow_host">allow_host()</a> · <a href="#allow_host_pattern">allow_host_pattern()</a> · <a href="/sdk/rust/networking#violationaction">ViolationAction</a></p>

Matches a destination host anywhere a secret policy needs one. Allow-list patterns decide where the real value may be substituted; passthrough patterns (on ViolationAction) decide where the placeholder may be forwarded unchanged. Passthrough patterns never make a secret eligible for substitution. Exact and wildcard allow-list entries are pinned to observed DNS answers and TLS identity; Any is the only allow-list pattern that skips this pinning.

VariantBuilder methodsMatches
Exact(String)allow_host(), passthrough_host()That exact host, ASCII case-insensitive
Wildcard(String)allow_host_pattern(), passthrough_host_pattern()The suffix and its subdomains, e.g. *.example.com
Anyallow_any_host_dangerous(true), passthrough_all_hosts(true)Every host
MethodSignatureDescription
matchesfn matches(&self, hostname: &str) -> boolWhether hostname matches this pattern (ASCII case-insensitive).

SecretEntry

<div className="msb-tags"><span className="msb-tag is-type">struct</span></div> <p className="msb-backref">returned by <a href="#build">SecretBuilder::build()</a> · used by <a href="#secret_entry">secret_entry()</a></p>

The materialized, serializable form of a secret that is handed to the network engine. The real value is redacted from the Debug output. Built for you by SecretBuilder; construct it directly only when you need full control over the serialized shape, then pass it to secret_entry().

FieldTypeDescription
env_varStringEnvironment variable holding the placeholder. Non-empty, no = or NUL.
valueStringThe real secret value (never enters the guest).
placeholderStringWhat the guest sees. Non-empty, at most 1024 bytes, no NUL/CR/LF.
allowed_hostsVec<HostPattern>Hosts allowed to receive the real value.
injectionSecretInjectionWhere in the request the value may be substituted.
on_violationOption<ViolationAction>Per-secret violation override. None falls back to the sandbox-wide action.
require_tls_identityboolRequire verified TLS identity before substituting. Default: true.
MethodSignatureDescription
validatefn validate(&self, secret_index: usize) -> Result<(), SecretConfigError>Validate this entry's env var, allowed hosts, and placeholder.

SecretInjection

<div className="msb-tags"><span className="msb-tag is-type">struct</span></div> <p className="msb-backref">used by <a href="#secretentry">SecretEntry</a></p>

Which parts of an outbound HTTP request the placeholder may be substituted in. Set through the inject_* methods on SecretBuilder.

FieldTypeDefaultDescription
headersbooltrueSubstitute anywhere in HTTP headers.
basic_authbooltrueSubstitute inside decoded Authorization: Basic credentials.
query_paramsboolfalseSubstitute in the URL query string.
bodyboolfalseSubstitute in HTTP/1 request bodies (see inject_body() for limits).

ViolationAction

<div className="msb-tags"><span className="msb-tag is-type">enum</span></div> <p className="msb-backref">used by <a href="#secretentry">SecretEntry</a> · <a href="#on_violation">on_violation()</a> · <a href="/sdk/rust/networking#violationactionbuilder">ViolationActionBuilder</a></p>

What happens when the guest sends a request containing a secret placeholder to a host that is not in the secret's substitution allow list. Built through ViolationActionBuilder, set globally via NetworkBuilder::on_secret_violation() or per secret via SecretBuilder::on_violation(). Also documented on the Networking page.

VariantDescription
BlockSilently drop the request. The guest sees a connection reset.
BlockAndLogDrop the request and emit a warning log on the host side. This is the default.
BlockAndTerminateDrop the request, log an error, and shut down the entire sandbox.
Passthrough(Vec<HostPattern>)Forward matching hosts with the placeholder unchanged. Non-matching hosts fall back to the default secret violation action.

SecretConfigError

<div className="msb-tags"><span className="msb-tag is-type">enum</span></div> <p className="msb-backref">returned by <a href="#secretentry">SecretEntry::validate()</a></p>

Why a secret entry failed validation. Each variant carries the offending secret_index. The placeholder ceiling is the public constant MAX_SECRET_PLACEHOLDER_BYTES = 1024.

VariantDescription
EmptyEnvVarThe environment variable name is empty.
EnvVarContainsEqualsThe environment variable name contains =.
EnvVarContainsNulThe environment variable name contains NUL.
MissingAllowedHostsNo allowed hosts were configured.
EmptyPlaceholderThe placeholder is empty.
PlaceholderTooLongThe placeholder exceeds MAX_SECRET_PLACEHOLDER_BYTES (carries actual_bytes, max_bytes).
PlaceholderContainsNulThe placeholder contains NUL.
PlaceholderContainsLineBreakThe placeholder contains CR or LF.