docs/sdk/rust/networking.mdx
Configure a sandbox's network stack: a first-match-wins egress/ingress policy, published ports, DNS interception, TLS interception, and secret-violation handling. See Networking for the conceptual overview and TLS Interception for proxy details.
<div className="msb-glance"> <p className="msb-gl"><span className="msb-dot static"></span>Static · NetworkPolicy<span className="msb-ct">5</span></p> <a className="msb-row" href="#networkpolicybuilder"><span className="msb-rn">NetworkPolicy::builder()</span><span className="msb-rg">start the fluent builder</span></a> <a className="msb-row" href="#networkpolicynone"><span className="msb-rn">NetworkPolicy::none()</span><span className="msb-rg">deny everything</span></a> <a className="msb-row" href="#networkpolicyallow_all"><span className="msb-rn">NetworkPolicy::allow_all()</span><span className="msb-rg">allow everything</span></a> <a className="msb-row" href="#networkpolicypublic_only"><span className="msb-rn">NetworkPolicy::public_only()</span><span className="msb-rg">public internet only (default)</span></a> <a className="msb-row" href="#networkpolicynon_local"><span className="msb-rn">NetworkPolicy::non_local()</span><span className="msb-rg">public + private, no local</span></a> <p className="msb-gl"><span className="msb-dot instance"></span>Instance · NetworkPolicy<span className="msb-ct">8</span></p> <a className="msb-row" href="#policy-allow_domain"><span className="msb-rn">policy.allow_domain()</span><span className="msb-rg">prepend an allow-Domain rule</span></a> <a className="msb-row" href="#policy-deny_domain"><span className="msb-rn">policy.deny_domain()</span><span className="msb-rg">prepend a deny-Domain rule</span></a> <a className="msb-row" href="#policy-allow_domains"><span className="msb-rn">policy.allow_domains()</span><span className="msb-rg">prepend allow-Domain rules</span></a> <a className="msb-row" href="#policy-deny_domains"><span className="msb-rn">policy.deny_domains()</span><span className="msb-rg">prepend deny-Domain rules</span></a> <a className="msb-row" href="#policy-allow_domain_suffix"><span className="msb-rn">policy.allow_domain_suffix()</span><span className="msb-rg">prepend an allow-suffix rule</span></a> <a className="msb-row" href="#policy-deny_domain_suffix"><span className="msb-rn">policy.deny_domain_suffix()</span><span className="msb-rg">prepend a deny-suffix rule</span></a> <a className="msb-row" href="#policy-allow_domain_suffixes"><span className="msb-rn">policy.allow_domain_suffixes()</span><span className="msb-rg">prepend allow-suffix rules</span></a> <a className="msb-row" href="#policy-deny_domain_suffixes"><span className="msb-rn">policy.deny_domain_suffixes()</span><span className="msb-rg">prepend deny-suffix rules</span></a> <p className="msb-gl"><span className="msb-dot builder"></span>Builder · NetworkBuilder<span className="msb-ct">15</span></p> <div className="msb-chiprow"> <a className="msb-chip" href="#policy">.policy()</a> <a className="msb-chip" href="#nb-port">.port()</a> <a className="msb-chip" href="#port_udp">.port_udp()</a> <a className="msb-chip" href="#port_bind">.port_bind()</a> <a className="msb-chip" href="#port_udp_bind">.port_udp_bind()</a> <a className="msb-chip" href="#dns">.dns()</a> <a className="msb-chip" href="#tls">.tls()</a> <a className="msb-chip" href="#trust_host_cas">.trust_host_cas()</a> <a className="msb-chip" href="#max_connections">.max_connections()</a> <a className="msb-chip" href="#ipv4_pool">.ipv4_pool()</a> <a className="msb-chip" href="#ipv6_pool">.ipv6_pool()</a> <a className="msb-chip" href="#interface">.interface()</a> <a className="msb-chip" href="#enabled">.enabled()</a> <a className="msb-chip" href="#on_secret_violation">.on_secret_violation()</a> <a className="msb-chip" href="#secret">.secret()</a> </div> <p className="msb-gl"><span className="msb-dot builder"></span>Builder · NetworkPolicyBuilder<span className="msb-ct">9</span></p> <div className="msb-chiprow"> <a className="msb-chip" href="#default_deny">.default_deny()</a> <a className="msb-chip" href="#default_allow">.default_allow()</a> <a className="msb-chip" href="#default_egress">.default_egress()</a> <a className="msb-chip" href="#default_ingress">.default_ingress()</a> <a className="msb-chip" href="#egress">.egress()</a> <a className="msb-chip" href="#ingress">.ingress()</a> <a className="msb-chip" href="#any">.any()</a> <a className="msb-chip" href="#rule">.rule()</a> <a className="msb-chip" href="#build">.build()</a> </div> <p className="msb-gl"><span className="msb-dot builder"></span>Builder · RuleBuilder<span className="msb-ct">32</span></p> <div className="msb-chiprow"> <a className="msb-chip" href="#rb-egress">.egress()</a> <a className="msb-chip" href="#rb-ingress">.ingress()</a> <a className="msb-chip" href="#rb-any">.any()</a> <a className="msb-chip" href="#tcp">.tcp()</a> <a className="msb-chip" href="#udp">.udp()</a> <a className="msb-chip" href="#port-1">.port()</a> <a className="msb-chip" href="#allow_public">.allow_public()</a> <a className="msb-chip" href="#allow_private">.allow_private()</a> <a className="msb-chip" href="#allow_host">.allow_host()</a> <a className="msb-chip" href="#allow_local">.allow_local()</a> <a className="msb-chip" href="#allow_domains-1">.allow_domains()</a> <a className="msb-chip" href="#rb-allow">.allow()</a> <a className="msb-chip" href="#rb-deny">.deny()</a> <a className="msb-more" href="#rulebuilder">+ 19 more in the RuleBuilder section</a> </div> <p className="msb-gl"><span className="msb-dot builder"></span>Builder · RuleDestinationBuilder<span className="msb-ct">6</span></p> <div className="msb-chiprow"> <a className="msb-chip" href="#rd-ip">.ip()</a> <a className="msb-chip" href="#rd-cidr">.cidr()</a> <a className="msb-chip" href="#rd-domain">.domain()</a> <a className="msb-chip" href="#rd-domain_suffix">.domain_suffix()</a> <a className="msb-chip" href="#rd-group">.group()</a> <a className="msb-chip" href="#rd-any">.any()</a> </div> <p className="msb-gl"><span className="msb-dot builder"></span>Builder · DnsBuilder · TlsBuilder · ViolationActionBuilder<span className="msb-ct">16</span></p> <div className="msb-chiprow"> <a className="msb-chip" href="#nameservers">.nameservers()</a> <a className="msb-chip" href="#query_timeout_ms">.query_timeout_ms()</a> <a className="msb-chip" href="#rebind_protection">.rebind_protection()</a> <a className="msb-chip" href="#bypass">.bypass()</a> <a className="msb-chip" href="#intercepted_ports">.intercepted_ports()</a> <a className="msb-chip" href="#verify_upstream">.verify_upstream()</a> <a className="msb-chip" href="#block_quic">.block_quic()</a> <a className="msb-chip" href="#intercept_ca_cert">.intercept_ca_cert()</a> <a className="msb-chip" href="#intercept_ca_key">.intercept_ca_key()</a> <a className="msb-chip" href="#upstream_ca_cert">.upstream_ca_cert()</a> <a className="msb-chip" href="#block_and_log">.block_and_log()</a> <a className="msb-chip" href="#passthrough_host">.passthrough_host()</a> <a className="msb-chip" href="#passthrough_host_pattern">.passthrough_host_pattern()</a> <a className="msb-chip" href="#passthrough_all_hosts">.passthrough_all_hosts()</a> <a className="msb-more" href="#violationactionbuilder">+ 2 more in the ViolationActionBuilder section</a> </div> <p className="msb-gl"><span className="msb-dot type"></span>Types</p> <div className="msb-chiprow"> <a className="msb-typepill" href="#networkpolicy">NetworkPolicy</a> <a className="msb-typepill" href="#rule">Rule</a> <a className="msb-typepill" href="#action">Action</a> <a className="msb-typepill" href="#direction">Direction</a> <a className="msb-typepill" href="#destination">Destination</a> <a className="msb-typepill" href="#destinationgroup">DestinationGroup</a> <a className="msb-typepill" href="#protocol">Protocol</a> <a className="msb-typepill" href="#portrange">PortRange</a> <a className="msb-typepill" href="#domainname">DomainName</a> <a className="msb-typepill" href="#nameserver">Nameserver</a> <a className="msb-typepill" href="#interfaceoverrides">InterfaceOverrides</a> <a className="msb-typepill" href="#builderror">BuildError</a> <a className="msb-typepill" href="#violationaction">ViolationAction</a> <a className="msb-typepill" href="/sdk/rust/secrets#hostpattern">HostPattern</a> </div> </div> <p className="msb-label" id="typical-flow">Typical flow</p>use microsandbox::{NetworkPolicy, Sandbox};
let policy = NetworkPolicy::builder() // 1. compose a policy
.default_deny()
.egress(|e| e.tcp().port(443).allow_public())
.rule(|r| r.any().deny().ip("198.51.100.5"))
.build()?;
let sb = Sandbox::builder("api")
.image("python")
.network(|n| n // 2. wire it into the sandbox
.policy(policy)
.port(8080, 80)
.dns(|d| d.rebind_protection(true)))
.create()
.await?;
The default policy denies egress except for an implicit allow-public rule (plus DNS), and allows ingress with no rules. See the defaults rationale for the asymmetry. NetworkPolicy and the builders live in microsandbox_network; NetworkPolicy is also re-exported from the crate root as microsandbox::NetworkPolicy.
A NetworkPolicy is an ordered rule list plus two per-direction defaults, evaluated first-match-wins. The presets below construct common shapes directly; for anything custom, start from builder().
fn builder() -> NetworkPolicyBuilder
Start the fluent NetworkPolicyBuilder. The primary construction path: string inputs (.ip, .cidr, .domain, .domain_suffix) are stored raw and parsed at build(), so the chain stays clean and the first parse or validation failure surfaces as BuildError.
use microsandbox::NetworkPolicy;
let policy = NetworkPolicy::builder()
.default_deny()
.egress(|e| e.tcp().port(443).allow_public().allow_private())
.build()?;
fn none() -> NetworkPolicy
No network access: deny everything in both directions, no rules. This is the policy set by SandboxBuilder::disable_network().
fn allow_all() -> NetworkPolicy
Unrestricted network access: allow everything in both directions, no rules.
fn public_only() -> NetworkPolicy
Public internet only, and the Default policy. Egress defaults to deny but allows DNS to the gateway forwarder and any Public destination; private, loopback, link-local, and metadata are denied. Ingress defaults to allow, preserving unfiltered published-port behavior.
fn non_local() -> NetworkPolicy
Non-local access: like public_only() but also allows egress to Private / LAN ranges. Loopback, link-local, and metadata stay denied; ingress defaults to allow.
These methods consume self and return a modified policy, so they chain off a preset or a built policy. Each prepends its rules, so a later deny outranks a catch-all allow like allow public under first-match-wins. All return Result<NetworkPolicy, DomainNameError> because the names are parsed eagerly.
fn allow_domain<S: AsRef<str>>(self, name: S) -> Result<NetworkPolicy, DomainNameError>
Prepend a single allow-Domain egress rule. Single-name sugar over allow_domains().
let policy = NetworkPolicy::public_only().allow_domain("api.openai.com")?;
fn deny_domain<S: AsRef<str>>(self, name: S) -> Result<NetworkPolicy, DomainNameError>
Prepend a single deny-Domain egress rule. Single-name sugar over deny_domains().
fn allow_domains<I, S>(self, names: I) -> Result<NetworkPolicy, DomainNameError>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
Prepend one allow-Domain egress rule per name.
let policy = NetworkPolicy::default()
.allow_domains(["pypi.org", "files.pythonhosted.org"])?;
fn deny_domains<I, S>(self, names: I) -> Result<NetworkPolicy, DomainNameError>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
Prepend one deny-Domain egress rule per name. Prepending lets the denies outrank catch-all allows.
let policy = NetworkPolicy::allow_all()
.deny_domains(["evil.com", "tracker.example"])?;
fn allow_domain_suffix<S: AsRef<str>>(self, suffix: S) -> Result<NetworkPolicy, DomainNameError>
Prepend a single allow-DomainSuffix egress rule. Single-suffix sugar over allow_domain_suffixes().
fn deny_domain_suffix<S: AsRef<str>>(self, suffix: S) -> Result<NetworkPolicy, DomainNameError>
Prepend a single deny-DomainSuffix egress rule. Single-suffix sugar over deny_domain_suffixes().
fn allow_domain_suffixes<I, S>(self, suffixes: I) -> Result<NetworkPolicy, DomainNameError>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
Prepend one allow-DomainSuffix egress rule per suffix. Suffixes match the apex domain and every subdomain (label-aligned).
fn deny_domain_suffixes<I, S>(self, suffixes: I) -> Result<NetworkPolicy, DomainNameError>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
Prepend one deny-DomainSuffix egress rule per suffix.
let policy = NetworkPolicy::allow_all()
.deny_domain_suffixes([".ads.example", ".doubleclick.net"])?;
Fluent builder for NetworkPolicy, obtained via NetworkPolicy::builder(). Defaults and rule-batch closures interleave; the build is deferred. The closure signature for rule() / egress() / ingress() / any() is FnOnce(&mut RuleBuilder) -> &mut RuleBuilder. A chain ending in any rule-adder (.allow_public(), .deny().ip(...), etc.) returns the builder reference and satisfies the bound; multi-statement bodies end with an explicit r return.
State setters inside a closure (.tcp(), .port()) accumulate eagerly and are not reset between rule-adders, so a single closure can fan one state into several rules. Use separate closures for rules that need different state. See State accumulation for the rationale.
fn default_deny(self) -> Self
Set both default_egress and default_ingress to Deny.
fn default_allow(self) -> Self
Set both default_egress and default_ingress to Allow.
fn default_egress(self, action: Action) -> Self
Per-direction override for the egress default action.
<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>action</code><a className="msb-type" href="#action">Action</a></div> <div className="msb-param-desc">Default action for egress.</div> </div> </div>fn default_ingress(self, action: Action) -> Self
Per-direction override for the ingress default action.
<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>action</code><a className="msb-type" href="#action">Action</a></div> <div className="msb-param-desc">Default action for ingress.</div> </div> </div>fn egress<F>(self, f: F) -> Self
where
F: for<'a> FnOnce(&'a mut RuleBuilder) -> &'a mut RuleBuilder
Sugar for rule() with direction pre-set to Egress.
NetworkPolicy::builder()
.default_deny()
.egress(|e| e.tcp().port(443).allow_public())
.build()?;
fn ingress<F>(self, f: F) -> Self
where
F: for<'a> FnOnce(&'a mut RuleBuilder) -> &'a mut RuleBuilder
Sugar for rule() with direction pre-set to Ingress.
fn any<F>(self, f: F) -> Self
where
F: for<'a> FnOnce(&'a mut RuleBuilder) -> &'a mut RuleBuilder
Sugar for rule() with direction pre-set to Any. Rules committed inside apply in both directions.
fn rule<F>(self, f: F) -> Self
where
F: for<'a> FnOnce(&'a mut RuleBuilder) -> &'a mut RuleBuilder
Open a multi-rule batch closure. Direction must be set inside via .egress(), .ingress(), or .any() before any rule-adder, otherwise build() returns BuildError::DirectionNotSet.
NetworkPolicy::builder()
.rule(|r| r.egress().tcp().port(443).allow().domain("api.example.com"))
.build()?;
fn build(self) -> Result<NetworkPolicy, BuildError>
Consume the builder and produce a NetworkPolicy. Lazy-parses every .ip() / .cidr() / .domain() / .domain_suffix() input, validates the direction-set and ICMP-egress-only invariants, and emits a tracing::warn! for each shadowed rule pair (a rule fully covered by an earlier one in the same direction; only Ip / Cidr / Group destinations are checked). Builds still succeed when a shadow is detected. Returns the first BuildError encountered.
The mutable builder handed to a NetworkPolicyBuilder rule-batch closure. Direction, protocol, and port setters return &mut Self and accumulate eagerly; rule-adders commit one rule each using the current state. Protocols and ports have set semantics, so duplicates dedupe.
<a id="rb-egress"></a>
fn egress(&mut self) -> &mut Self
Set direction to Egress for subsequent rule-adders. Last-write-wins.
<a id="rb-ingress"></a>
fn ingress(&mut self) -> &mut Self
Set direction to Ingress for subsequent rule-adders. Last-write-wins.
<a id="rb-any"></a>
fn any(&mut self) -> &mut Self
Set direction to Any for subsequent rule-adders. Rules committed after this apply in both directions. Last-write-wins.
fn tcp(&mut self) -> &mut Self
Add Tcp to the protocols set.
fn udp(&mut self) -> &mut Self
Add Udp to the protocols set.
fn icmpv4(&mut self) -> &mut Self
Add Icmpv4 to the protocols set. Egress-only: an ICMP protocol on an Ingress or Any rule fails build with BuildError::IngressDoesNotSupportIcmp.
fn icmpv6(&mut self) -> &mut Self
Add Icmpv6 to the protocols set. Egress-only; same rule as icmpv4().
<a id="port-1"></a>
fn port(&mut self, port: u16) -> &mut Self
Add a single port to the ports set. Always guest-side (egress destination port / ingress listening port).
<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>port</code><span className="msb-type">u16</span></div> <div className="msb-param-desc">Port number.</div> </div> </div>fn port_range(&mut self, lo: u16, hi: u16) -> &mut Self
Add an inclusive port range.
<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>lo</code><span className="msb-type">u16</span></div> <div className="msb-param-desc">Lower bound (inclusive).</div> </div> <div className="msb-param"> <div className="msb-param-key"><code>hi</code><span className="msb-type">u16</span></div> <div className="msb-param-desc">Upper bound (inclusive). <code>lo > hi</code> records <a href="#builderror">BuildError::InvalidPortRange</a>.</div> </div> </div>fn ports<I: IntoIterator<Item = u16>>(&mut self, ports: I) -> &mut Self
Add multiple single ports. Equivalent to calling port() once per element.
fn allow_public(&mut self) -> &mut Self
Commit an allow rule for the Public group: every IP not in another named category. A matching deny_public() exists for each allow_* group adder below.
fn allow_private(&mut self) -> &mut Self
Allow the Private group (RFC1918 + ULA + CGN).
fn allow_loopback(&mut self) -> &mut Self
Allow the Loopback group (127.0.0.0/8, ::1): the guest's own loopback, not the host. To reach a service on the host's localhost use allow_host() instead. See the loopback-vs-host trap.
fn allow_link_local(&mut self) -> &mut Self
Allow the LinkLocal group (169.254.0.0/16, fe80::/10). Excludes the metadata IP 169.254.169.254.
fn allow_meta(&mut self) -> &mut Self
Allow the Metadata group (169.254.169.254). Dangerous on cloud hosts: exposes IAM credentials.
fn allow_multicast(&mut self) -> &mut Self
Allow the Multicast group (224.0.0.0/4, ff00::/8).
fn allow_host(&mut self) -> &mut Self
Allow the Host group: per-sandbox gateway IPs that back host.microsandbox.internal. This is the right shortcut for "let the sandbox reach my host's localhost", not allow_loopback().
fn deny_public(&mut self) -> &mut Self
Deny the Public group. Per-group deny_* adders mirror the allow_* set: deny_private(), deny_loopback(), deny_link_local(), deny_meta(), deny_multicast(), and deny_host().
fn allow_local(&mut self) -> &mut Self
Commit three allow rules atomically: Loopback + LinkLocal + Host. Each uses the closure's current state. Metadata is intentionally excluded; opt in via allow_meta() separately.
fn deny_local(&mut self) -> &mut Self
Commit three deny rules atomically: Loopback + LinkLocal + Host. Metadata is intentionally excluded.
<a id="allow_domains-1"></a>
fn allow_domains<I, S>(&mut self, names: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: Into<String>
Add one allow-Domain rule per name, inheriting the closure's current direction / protocol / port state. Lazy-parse: invalid names surface as BuildError::InvalidDomain from build().
NetworkPolicy::builder()
.default_allow()
.egress(|e| e
.deny_domains(["evil.com", "tracker.example"])
.deny_domain_suffixes([".ads.example", ".doubleclick.net"]))
.build()?;
fn deny_domains<I, S>(&mut self, names: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: Into<String>
Add one deny-Domain rule per name.
fn allow_domain_suffixes<I, S>(&mut self, suffixes: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: Into<String>
Add one allow-DomainSuffix rule per suffix.
fn deny_domain_suffixes<I, S>(&mut self, suffixes: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: Into<String>
Add one deny-DomainSuffix rule per suffix.
<a id="rb-allow"></a>
fn allow(&mut self) -> RuleDestinationBuilder<'_>
Begin an explicit-destination rule with action Allow. The returned RuleDestinationBuilder requires exactly one destination call to commit; dropping it without one adds no rule.
NetworkPolicy::builder()
.egress(|e| e.tcp().port(443).allow().domain("api.example.com"))
.any(|a| a.deny().cidr("198.51.100.0/24"))
.build()?;
<a id="rb-deny"></a>
fn deny(&mut self) -> RuleDestinationBuilder<'_>
Begin an explicit-destination rule with action Deny.
Returned by RuleBuilder::allow() / RuleBuilder::deny(). Requires exactly one destination method call to commit the rule, then returns the &mut RuleBuilder so the chain continues. The type is #[must_use]: dropping it without a destination call adds no rule.
<a id="rd-ip"></a>
fn ip(self, ip: impl Into<String>) -> &'a mut RuleBuilder
Commit with Destination::Cidr of the IP as /32 (v4) or /128 (v6). The string is parsed at build(); invalid values surface as BuildError::InvalidIp.
<a id="rd-cidr"></a>
fn cidr(self, cidr: impl Into<String>) -> &'a mut RuleBuilder
Commit with Destination::Cidr. Invalid values surface as BuildError::InvalidCidr.
<a id="rd-domain"></a>
fn domain(self, domain: impl Into<String>) -> &'a mut RuleBuilder
Commit with Destination::Domain. Matches only when a cached hostname for the remote IP equals this name (after canonicalization).
<a id="rd-domain_suffix"></a>
fn domain_suffix(self, suffix: impl Into<String>) -> &'a mut RuleBuilder
Commit with Destination::DomainSuffix. Matches the apex domain itself and any subdomain. A single-label suffix (e.g. com) is rejected at build as BuildError::InvalidDomain.
<a id="rd-group"></a>
fn group(self, group: DestinationGroup) -> &'a mut RuleBuilder
Commit with Destination::Group for callers who already hold a DestinationGroup value.
<a id="rd-any"></a>
fn any(self) -> &'a mut RuleBuilder
Commit with Destination::Any: matches every remote.
Builder for the sandbox's network stack, used in SandboxBuilder::network(|n| n...). Every setter returns Self, so calls chain. Errors accumulated by nested builders cascade up: the outermost SandboxBuilder::build() surfaces them as MicrosandboxError::NetworkBuilder(BuildError).
fn policy(self, policy: NetworkPolicy) -> Self
Set the network access policy. Pass a preset or a builder-constructed NetworkPolicy.
.network(|n| n.policy(NetworkPolicy::builder().default_deny().build()?))
<a id="nb-port"></a>
fn port(self, host_port: u16, guest_port: u16) -> Self
Publish a TCP port from the sandbox to the host. The default host bind address is 127.0.0.1. Equivalent to SandboxBuilder::port().
fn port_udp(self, host_port: u16, guest_port: u16) -> Self
Publish a UDP port. The default host bind address is 127.0.0.1.
fn port_bind(self, host_bind: IpAddr, host_port: u16, guest_port: u16) -> Self
Publish a TCP port on a specific host bind address, such as 0.0.0.0.
fn port_udp_bind(self, host_bind: IpAddr, host_port: u16, guest_port: u16) -> Self
Publish a UDP port on a specific host bind address.
fn dns(self, f: impl FnOnce(DnsBuilder) -> DnsBuilder) -> Self
Configure DNS interception. See DnsBuilder.
use microsandbox_network::dns::Nameserver;
.network(|n| n
.dns(|d| d
.nameservers(["1.1.1.1".parse::<Nameserver>()?])
.query_timeout_ms(3000)))
fn tls(self, f: impl FnOnce(TlsBuilder) -> TlsBuilder) -> Self
Configure TLS interception. See TlsBuilder.
fn trust_host_cas(self, enabled: bool) -> Self
Whether to ship the host's trusted root CAs into the guest at boot. Default: false. Opt in when egress HTTPS inside the sandbox needs to work behind corporate MITM proxies (Cloudflare Warp Zero Trust, Zscaler, Netskope, etc.): those proxies install a gateway CA on the host that's unknown to the guest's stock Mozilla bundle.
fn max_connections(self, max: usize) -> Self
Limit the maximum number of concurrent network connections from the sandbox. Default: 256.
fn ipv4_pool(self, pool: Ipv4Network) -> Self
Set the IPv4 pool used to derive per-sandbox /30 guest subnets. Defaults to 172.16.0.0/12. A pool with a prefix longer than /30 records BuildError::InvalidIpv4Pool.
fn ipv6_pool(self, pool: Ipv6Network) -> Self
Set the IPv6 pool used to derive per-sandbox /64 guest prefixes. Defaults to fd42:6d73:62::/48. A pool with a prefix longer than /64 records BuildError::InvalidIpv6Pool.
fn interface(self, overrides: InterfaceOverrides) -> Self
Override the guest interface settings wholesale: MAC, MTU, IPv4/IPv6 addresses, and the derivation pools. A low-level escape hatch. For the common case of changing only the address pools, prefer ipv4_pool() and ipv6_pool(), which validate the prefix. Unset fields fall back to values derived deterministically from the sandbox slot. See InterfaceOverrides.
fn enabled(self, enabled: bool) -> Self
Enable or disable networking. Default: true. To fully turn networking off, prefer SandboxBuilder::disable_network(), which also sets the policy to NetworkPolicy::none().
fn on_secret_violation(
self,
f: impl FnOnce(ViolationActionBuilder) -> ViolationActionBuilder,
) -> Self
Set the sandbox-wide action taken when a secret placeholder is detected in traffic to a host not in the secret's allow list. See ViolationActionBuilder.
.network(|n| n.on_secret_violation(|v| v
.block_and_log()
.passthrough_host("api.anthropic.com")
.passthrough_host_pattern("*.example.com")))
Passthrough hosts receive the placeholder unchanged. They do not receive real secret values.
fn secret(self, f: impl FnOnce(SecretBuilder) -> SecretBuilder) -> Self
Add a secret via a closure builder. Mirrors SandboxBuilder::secret(). See SecretBuilder for the full API. A companion secret_env(env_var, value, placeholder, allowed_host) shorthand and secret_entry(SecretEntry) are also available on NetworkBuilder.
Builder for DNS interception, used in NetworkBuilder::dns(|d| d...). Owns rebind protection, nameserver pinning, and the per-query timeout. Every setter returns Self.
fn nameservers<I>(self, nameservers: I) -> Self
where
I: IntoIterator,
I::Item: Into<Nameserver>
Set the upstream nameservers to forward DNS queries to. Replaces any previously-set nameservers. When empty, the interceptor falls back to the host's /etc/resolv.conf (or, on macOS, the SystemConfiguration dynamic store). Each element converts into Nameserver: a SocketAddr, an IpAddr, or a parsed string via "dns.google:53".parse::<Nameserver>()?.
fn query_timeout_ms(self, ms: u64) -> Self
Set the per-DNS-query timeout in milliseconds. Default: 5000.
fn rebind_protection(self, enabled: bool) -> Self
When enabled, DNS responses that resolve to private IP addresses are blocked, preventing DNS rebinding attacks. Default: true.
Builder for TLS interception, used in NetworkBuilder::tls(|t| t...). Creating it enables interception. Every setter returns Self.
fn bypass(self, pattern: impl Into<String>) -> Self
Skip TLS interception for hosts matching this glob (e.g. "*.internal.corp"). Use for domains with certificate pinning. Can be called multiple times.
fn intercepted_ports(self, ports: Vec<u16>) -> Self
TCP ports where TLS interception is active. Default: [443].
fn verify_upstream(self, verify: bool) -> Self
Whether the proxy verifies upstream server certificates. Default: true. Set to false only for self-signed servers.
fn block_quic(self, block: bool) -> Self
Block QUIC/HTTP3 on intercepted ports, forcing TCP/TLS fallback. Default: true.
fn intercept_ca_cert(self, path: impl Into<PathBuf>) -> Self
PEM file used as the intercepting CA's certificate. Pair with intercept_ca_key() to provide a stable CA across sandbox restarts. If unset, a CA is auto-generated and persisted.
fn intercept_ca_key(self, path: impl Into<PathBuf>) -> Self
PEM file used as the intercepting CA's private key.
fn upstream_ca_cert(self, path: impl Into<PathBuf>) -> Self
PEM file with extra root CAs the proxy should trust when verifying upstream servers. Useful for self-signed or private upstream CAs. Can be called multiple times.
Builder for secret-violation behavior, used by NetworkBuilder::on_secret_violation() and SecretBuilder::on_violation(). A blocking call (block, block_and_log, block_and_terminate) replaces any accumulated passthrough hosts; passthrough calls accumulate. When passthrough hosts are configured, non-matching hosts use the default action. Every setter returns Self. Produces a ViolationAction.
fn block(self) -> Self
Block the request silently.
fn block_and_log(self) -> Self
Block the request and emit a warning log on the host. This is the ViolationAction default.
fn block_and_terminate(self) -> Self
Block the request and terminate the entire sandbox.
fn passthrough_host(self, host: impl Into<String>) -> Self
Allow an exact host to receive secret placeholders unchanged (no substitution).
<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<String></span></div> <div className="msb-param-desc">Exact host.</div> </div> </div>fn passthrough_host_pattern(self, pattern: impl Into<String>) -> Self
Allow hosts matching a wildcard pattern (e.g. *.example.com) to receive placeholders unchanged.
fn passthrough_all_hosts(self, i_understand_the_risk: bool) -> Self
Allow any host to receive placeholders unchanged. Takes effect only when i_understand_the_risk is true.
An ordered rule list plus two per-direction defaults, evaluated first-match-wins. Egress evaluation considers rules where direction ∈ {Egress, Any}; ingress considers {Ingress, Any}. If no rule matches, the direction-specific default applies. Default is public_only().
| Field | Type | Description |
|---|---|---|
| default_egress | Action | Action when no egress-applicable rule matches |
| default_ingress | Action | Action when no ingress-applicable rule matches |
| rules | Vec<Rule> | Ordered rules; first match wins per direction |
A single policy rule. The destination interpretation is direction-dependent: egress destination, or ingress peer/source. ports is always the guest-side port (egress destination port / ingress listening port).
| Field | Type | Description |
|---|---|---|
| direction | Direction | Which evaluator considers this rule |
| destination | Destination | Target filter |
| protocols | Vec<Protocol> | Set semantics; empty = any protocol |
| ports | Vec<PortRange> | Set semantics; empty = any port |
| action | Action | What to do on match |
Convenience constructors build any-protocol, any-port rules:
| Method | Description |
|---|---|
Rule::allow_egress(destination) | Allow rule, direction Egress |
Rule::deny_egress(destination) | Deny rule, direction Egress |
Rule::allow_ingress(destination) | Allow rule, direction Ingress |
Rule::deny_ingress(destination) | Deny rule, direction Ingress |
Rule::allow_any(destination) | Allow rule, direction Any |
Rule::deny_any(destination) | Deny rule, direction Any |
Rule::allow_dns() | Allow plain DNS (UDP/53 + TCP/53) to the gateway forwarder (Group::Host); the one-liner for opening DNS under deny-by-default. See DNS as egress |
| Value | Wire format | Description |
|---|---|---|
Allow | "allow" | Permit the traffic |
Deny | "deny" | Drop the traffic silently |
| Value | Wire format | Description |
|---|---|---|
Egress | "egress" | Traffic leaving the sandbox |
Ingress | "ingress" | Traffic entering the sandbox (via published ports) |
Any | "any" | Rule applies in either direction |
| Variant | Description |
|---|---|
Any | Match any address |
Cidr(IpNetwork) | Match a CIDR range (e.g. 10.0.0.0/8); single IPs are stored as /32 or /128 |
Domain(DomainName) | Match an exact domain (e.g. example.com) when a cached hostname for the remote IP equals it |
DomainSuffix(DomainName) | Match the apex domain and every subdomain (e.g. example.com and api.example.com) |
Group(DestinationGroup) | Match a predefined address group |
Groups are disjoint with one carve-out: Metadata takes precedence over LinkLocal for 169.254.169.254, and Host over Private when the gateway IPs sit in CGN/ULA ranges.
| Value | Wire format | Matches |
|---|---|---|
Public | "public" | Complement of the other categories: every address not in any other group |
Loopback | "loopback" | 127.0.0.0/8, ::1 (the guest's own loopback, not the host) |
Private | "private" | RFC1918 (10/8, 172.16/12, 192.168/16) + CGN (100.64/10) + ULA (fc00::/7) |
LinkLocal | "link_local" | 169.254.0.0/16, fe80::/10 (excludes metadata) |
Metadata | "metadata" | Cloud metadata endpoint (169.254.169.254) |
Multicast | "multicast" | 224.0.0.0/4, ff00::/8 |
Host | "host" | Per-sandbox gateway IPs that back host.microsandbox.internal |
| Value | Wire format |
|---|---|
Tcp | "tcp" |
Udp | "udp" |
Icmpv4 | "icmpv4" |
Icmpv6 | "icmpv6" |
ICMP protocols are egress-only. A rule with direction Ingress or Any carrying an ICMP protocol fails build with BuildError::IngressDoesNotSupportIcmp.
An inclusive port range.
| Field | Type | Description |
|---|---|---|
| start | u16 | Start port (inclusive) |
| end | u16 | End port (inclusive) |
| Method | Description |
|---|---|
PortRange::single(port) | Match a single port |
PortRange::range(start, end) | Match an inclusive range |
contains(port) | Whether port falls within the range |
A validated DNS name. Construction goes through str::parse (or TryFrom<String>), which delegates to hickory_proto::rr::Name and canonicalizes the input (lowercased ASCII, leading and trailing dots stripped) so rule matching is a byte-wise compare against the DNS cache. Invalid inputs return a DomainNameError.
use microsandbox_network::policy::DomainName;
let exact: DomainName = "PyPI.Org.".parse()?; // -> "pypi.org"
let suffix: DomainName = ".example.com".parse()?; // -> "example.com"
Labels follow the permissive DNS grammar (RFC 2181 §11), so underscore-prefixed names like _service._tcp.example.com are accepted. The builder methods (.domain(&str), .domain_suffix(&str)) take strings and parse them lazily at build(), so callers rarely construct DomainName directly.
| Method | Description |
|---|---|
as_str() | Borrow the canonical string form |
try_into_suffix() | Validate for use as a DomainSuffix; single-label names (e.g. com) are rejected |
An upstream DNS server, either a literal address or a hostname resolved at interceptor startup via the host's OS resolver. Serializes as a single string. Construct via From<SocketAddr>, From<IpAddr>, or str::parse (errors with ParseNameserverError).
| Variant | Description |
|---|---|
Addr(SocketAddr) | Literal socket address, ready to use |
Host { host: String, port: u16 } | Hostname + port resolved at startup |
Accepted parse forms: 1.1.1.1, 1.1.1.1:5353, 2606:4700:4700::1111, [2606:4700:4700::1111]:53, dns.google, dns.google:53. A bare IP or hostname defaults to port 53.
Per-sandbox guest interface overrides. Every field is optional; an omitted field is derived deterministically from the sandbox slot. Most callers only touch the pools via ipv4_pool() / ipv6_pool() rather than constructing this directly.
| Field | Type | Description |
|---|---|---|
| mac | Option<[u8; 6]> | Guest MAC address. Default: derived from slot |
| mtu | Option<u16> | Interface MTU. Default: 1500 |
| ipv4_address | Option<Ipv4Addr> | Guest IPv4 address. Default: derived from slot within ipv4_pool |
| ipv4_pool | Option<Ipv4Network> | IPv4 pool guest subnets are derived from. Default: 172.16.0.0/12 |
| ipv6_address | Option<Ipv6Addr> | Guest IPv6 address. Default: derived from slot within ipv6_pool |
| ipv6_pool | Option<Ipv6Network> | IPv6 pool guest prefixes are derived from. Default: fd42:6d73:62::/48 |
Errors surfaced by the builders' build() methods. The same enum covers NetworkPolicy::builder(), DnsBuilder, and NetworkBuilder; the network and DNS builders accumulate lazily, so the first failure surfaces from the outermost build() in the chain.
| Variant | Cause |
|---|---|
DirectionNotSet { rule_index } | A rule was committed without .egress() / .ingress() / .any() |
MissingDestination { rule_index } | .allow() or .deny() was called but no destination method followed |
InvalidIp { rule_index, raw } | .ip(&str) got an unparseable value |
InvalidCidr { rule_index, raw } | .cidr(&str) got an unparseable value |
InvalidIpv4Pool { raw } | ipv4_pool() got a pool that can't hold a /30 sandbox subnet |
InvalidIpv6Pool { raw } | ipv6_pool() got a pool that can't hold a /64 sandbox prefix |
InvalidDomain { rule_index, raw, source } | .domain / .domain_suffix got a value that failed DomainName parse |
InvalidPortRange { rule_index, lo, hi } | .port_range(lo, hi) had lo > hi |
IngressDoesNotSupportIcmp { rule_index } | ICMP protocol on a non-egress rule |
InvalidSecretConfig { source } | A secret entry failed validation |
Inside SandboxBuilder::build(), BuildError is wrapped as MicrosandboxError::NetworkBuilder(BuildError).
Action taken when a secret placeholder is sent to a disallowed host. Also documented on the Secrets page, where it pairs with SecretBuilder.
| Value | Wire format | Description |
|---|---|---|
Block | "block" | Silently drop the request |
BlockAndLog | "block-and-log" | Drop the request and emit a warning log (the default) |
BlockAndTerminate | "block-and-terminate" | Drop the request, log an error, and shut down the sandbox |
Passthrough(Vec<HostPattern>) | "passthrough" | Forward matching hosts with the placeholder unchanged; non-matching hosts use the default action |