Skip to main content
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.

Typical flow

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.

NetworkPolicy static methods

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

NetworkPolicy::builder()

fn builder() -> NetworkPolicyBuilder
use microsandbox::NetworkPolicy;

let policy = NetworkPolicy::builder()
    .default_deny()
    .egress(|e| e.tcp().port(443).allow_public().allow_private())
    .build()?;
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.

Returns

Empty builder.

NetworkPolicy::none()

fn none() -> NetworkPolicy
No network access: deny everything in both directions, no rules. This is the policy set by SandboxBuilder::disable_network().

NetworkPolicy::allow_all()

fn allow_all() -> NetworkPolicy
Unrestricted network access: allow everything in both directions, no rules.

NetworkPolicy::public_only()

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.

NetworkPolicy::non_local()

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.

NetworkPolicy instance methods

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.

policy.allow_domain()

fn allow_domain<S: AsRef<str>>(self, name: S) -> Result<NetworkPolicy, DomainNameError>
let policy = NetworkPolicy::public_only().allow_domain("api.openai.com")?;
Prepend a single allow-Domain egress rule. Single-name sugar over allow_domains().

policy.deny_domain()

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

policy.allow_domains()

fn allow_domains<I, S>(self, names: I) -> Result<NetworkPolicy, DomainNameError>
where
    I: IntoIterator<Item = S>,
    S: AsRef<str>,
let policy = NetworkPolicy::default()
    .allow_domains(["pypi.org", "files.pythonhosted.org"])?;
Prepend one allow-Domain egress rule per name.

Parameters

namesIntoIterator<Item = AsRef<str>>
Exact domain names.

policy.deny_domains()

fn deny_domains<I, S>(self, names: I) -> Result<NetworkPolicy, DomainNameError>
where
    I: IntoIterator<Item = S>,
    S: AsRef<str>,
let policy = NetworkPolicy::allow_all()
    .deny_domains(["evil.com", "tracker.example"])?;
Prepend one deny-Domain egress rule per name. Prepending lets the denies outrank catch-all allows.

policy.allow_domain_suffix()

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

policy.deny_domain_suffix()

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

policy.allow_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).

policy.deny_domain_suffixes()

fn deny_domain_suffixes<I, S>(self, suffixes: I) -> Result<NetworkPolicy, DomainNameError>
where
    I: IntoIterator<Item = S>,
    S: AsRef<str>,
let policy = NetworkPolicy::allow_all()
    .deny_domain_suffixes([".ads.example", ".doubleclick.net"])?;
Prepend one deny-DomainSuffix egress rule per suffix.

NetworkPolicyBuilder

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.

.default_deny()

fn default_deny(self) -> Self
Set both default_egress and default_ingress to Deny.

.default_allow()

fn default_allow(self) -> Self
Set both default_egress and default_ingress to Allow.

.default_egress()

fn default_egress(self, action: Action) -> Self
Per-direction override for the egress default action.

Parameters

actionAction
Default action for egress.

.default_ingress()

fn default_ingress(self, action: Action) -> Self
Per-direction override for the ingress default action.

Parameters

actionAction
Default action for ingress.

.egress()

fn egress<F>(self, f: F) -> Self
where
    F: for<'a> FnOnce(&'a mut RuleBuilder) -> &'a mut RuleBuilder
NetworkPolicy::builder()
    .default_deny()
    .egress(|e| e.tcp().port(443).allow_public())
    .build()?;
Sugar for rule() with direction pre-set to Egress.

.ingress()

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.

.any()

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.

.rule()

fn rule<F>(self, f: F) -> Self
where
    F: for<'a> FnOnce(&'a mut RuleBuilder) -> &'a mut RuleBuilder
NetworkPolicy::builder()
    .rule(|r| r.egress().tcp().port(443).allow().domain("api.example.com"))
    .build()?;
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.

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

Returns

Validated policy.

RuleBuilder

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.

.egress()

fn egress(&mut self) -> &mut Self
Set direction to Egress for subsequent rule-adders. Last-write-wins.

.ingress()

fn ingress(&mut self) -> &mut Self
Set direction to Ingress for subsequent rule-adders. Last-write-wins.

.any()

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.

.tcp()

fn tcp(&mut self) -> &mut Self
Add Tcp to the protocols set.

.udp()

fn udp(&mut self) -> &mut Self
Add Udp to the protocols set.

.icmpv4()

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.

.icmpv6()

fn icmpv6(&mut self) -> &mut Self
Add Icmpv6 to the protocols set. Egress-only; same rule as icmpv4().

.port()

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

Parameters

portu16
Port number.

.port_range()

fn port_range(&mut self, lo: u16, hi: u16) -> &mut Self
Add an inclusive port range.

Parameters

lou16
Lower bound (inclusive).
hiu16
Upper bound (inclusive). lo > hi records BuildError::InvalidPortRange.

.ports()

fn ports<I: IntoIterator<Item = u16>>(&mut self, ports: I) -> &mut Self
Add multiple single ports. Equivalent to calling port() once per element.

.allow_public()

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.

.allow_private()

fn allow_private(&mut self) -> &mut Self
Allow the Private group (RFC1918 + ULA + CGN).

.allow_loopback()

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.

.allow_meta()

fn allow_meta(&mut self) -> &mut Self
Allow the Metadata group (169.254.169.254). Dangerous on cloud hosts: exposes IAM credentials.

.allow_multicast()

fn allow_multicast(&mut self) -> &mut Self
Allow the Multicast group (224.0.0.0/4, ff00::/8).

.allow_host()

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

.deny_public()

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

.allow_local()

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.

.deny_local()

fn deny_local(&mut self) -> &mut Self
Commit three deny rules atomically: Loopback + LinkLocal + Host. Metadata is intentionally excluded.

.allow_domains()

fn allow_domains<I, S>(&mut self, names: I) -> &mut Self
where
    I: IntoIterator<Item = S>,
    S: Into<String>
NetworkPolicy::builder()
    .default_allow()
    .egress(|e| e
        .deny_domains(["evil.com", "tracker.example"])
        .deny_domain_suffixes([".ads.example", ".doubleclick.net"]))
    .build()?;
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().

.deny_domains()

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.

.allow_domain_suffixes()

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.

.deny_domain_suffixes()

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.

.allow()

fn allow(&mut self) -> RuleDestinationBuilder<'_>
NetworkPolicy::builder()
    .egress(|e| e.tcp().port(443).allow().domain("api.example.com"))
    .any(|a| a.deny().cidr("198.51.100.0/24"))
    .build()?;
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.

.deny()

fn deny(&mut self) -> RuleDestinationBuilder<'_>
Begin an explicit-destination rule with action Deny.

RuleDestinationBuilder

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.

.ip()

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.

.cidr()

fn cidr(self, cidr: impl Into<String>) -> &'a mut RuleBuilder
Commit with Destination::Cidr. Invalid values surface as BuildError::InvalidCidr.

.domain()

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

.domain_suffix()

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.

.group()

fn group(self, group: DestinationGroup) -> &'a mut RuleBuilder
Commit with Destination::Group for callers who already hold a DestinationGroup value.

.any()

fn any(self) -> &'a mut RuleBuilder
Commit with Destination::Any: matches every remote.

NetworkBuilder

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

.policy()

fn policy(self, policy: NetworkPolicy) -> Self
.network(|n| n.policy(NetworkPolicy::builder().default_deny().build()?))
Set the network access policy. Pass a preset or a builder-constructed NetworkPolicy.

Parameters

Access policy.

.port()

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

Parameters

host_portu16
Port on the host.
guest_portu16
Port inside the sandbox.

.port_udp()

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.

.port_bind()

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.

Parameters

host_bindIpAddr
Host bind address.
host_portu16
Port on the host.
guest_portu16
Port inside the sandbox.

.port_udp_bind()

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.

.dns()

fn dns(self, f: impl FnOnce(DnsBuilder) -> DnsBuilder) -> Self
use microsandbox_network::dns::Nameserver;

.network(|n| n
    .dns(|d| d
        .nameservers(["1.1.1.1".parse::<Nameserver>()?])
        .query_timeout_ms(3000)))
Configure DNS interception. See DnsBuilder.

.tls()

fn tls(self, f: impl FnOnce(TlsBuilder) -> TlsBuilder) -> Self
Configure TLS interception. See TlsBuilder.

.trust_host_cas()

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.

.max_connections()

fn max_connections(self, max: usize) -> Self
Limit the maximum number of concurrent network connections from the sandbox. Default: 256.

Parameters

maxusize
Maximum concurrent connections.

.ipv4_pool()

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.

Parameters

poolIpv4Network
IPv4 pool, prefix /30 or shorter.

.ipv6_pool()

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.

.interface()

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.

Parameters

Guest interface overrides.

.enabled()

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

.on_secret_violation()

fn on_secret_violation(
    self,
    f: impl FnOnce(ViolationActionBuilder) -> ViolationActionBuilder,
) -> Self
.network(|n| n.on_secret_violation(|v| v
    .block_and_log()
    .passthrough_host("api.anthropic.com")
    .passthrough_host_pattern("*.example.com")))
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. Passthrough hosts receive the placeholder unchanged. They do not receive real secret values.

.secret()

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.

Parameters

Configure the secret.

DnsBuilder

Builder for DNS interception, used in NetworkBuilder::dns(|d| d...). Owns rebind protection, nameserver pinning, and the per-query timeout. Every setter returns Self.

.nameservers()

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>()?.

Parameters

nameserversIntoIterator<Item = Into<Nameserver>>
Upstream resolvers.

.query_timeout_ms()

fn query_timeout_ms(self, ms: u64) -> Self
Set the per-DNS-query timeout in milliseconds. Default: 5000.

.rebind_protection()

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.

TlsBuilder

Builder for TLS interception, used in NetworkBuilder::tls(|t| t...). Creating it enables interception. Every setter returns Self.

.bypass()

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.

Parameters

patternimpl Into<String>
Host glob. Supports exact match and *.suffix wildcards.

.intercepted_ports()

fn intercepted_ports(self, ports: Vec<u16>) -> Self
TCP ports where TLS interception is active. Default: [443].

.verify_upstream()

fn verify_upstream(self, verify: bool) -> Self
Whether the proxy verifies upstream server certificates. Default: true. Set to false only for self-signed servers.

.block_quic()

fn block_quic(self, block: bool) -> Self
Block QUIC/HTTP3 on intercepted ports, forcing TCP/TLS fallback. Default: true.

.intercept_ca_cert()

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.

.intercept_ca_key()

fn intercept_ca_key(self, path: impl Into<PathBuf>) -> Self
PEM file used as the intercepting CA’s private key.

.upstream_ca_cert()

fn upstream_ca_cert(self, path: impl Into<PathBuf>) -> Self
PEM file with extra root CAs the proxy should trust when verifying every upstream server. Useful for self-signed or private upstream CAs. Can be called multiple times.

.upstream_ca_cert_for()

fn upstream_ca_cert_for(
    self,
    pattern: impl Into<String>,
    path: impl Into<PathBuf>,
) -> Self
PEM file with extra root CAs the proxy should trust only when the upstream SNI matches pattern. Pattern syntax matches bypass(): exact hosts and *.suffix wildcards are supported.

.verify_upstream_for()

fn verify_upstream_for(
    self,
    pattern: impl Into<String>,
    verify: bool,
) -> Self
Whether the proxy verifies upstream server certificates only when the upstream SNI matches pattern. Pattern syntax matches bypass(): exact hosts and *.suffix wildcards are supported. Setting verify to false is the proxy-side equivalent of curl -k for matching hosts; TLS interception still runs.

ViolationActionBuilder

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.

.block()

fn block(self) -> Self
Block the request silently.

.block_and_log()

fn block_and_log(self) -> Self
Block the request and emit a warning log on the host. This is the ViolationAction default.

.block_and_terminate()

fn block_and_terminate(self) -> Self
Block the request and terminate the entire sandbox.

.passthrough_host()

fn passthrough_host(self, host: impl Into<String>) -> Self
Allow an exact host to receive secret placeholders unchanged (no substitution).

Parameters

hostimpl Into<String>
Exact host.

.passthrough_host_pattern()

fn passthrough_host_pattern(self, pattern: impl Into<String>) -> Self
Allow hosts matching a wildcard pattern (e.g. *.example.com) to receive placeholders unchanged.

.passthrough_all_hosts()

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.

Parameters

i_understand_the_riskbool
Must be true to take effect.

Types

NetworkPolicy

Built by NetworkPolicy::builder() · used by policy()

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().
FieldTypeDescription
default_egressActionAction when no egress-applicable rule matches
default_ingressActionAction when no ingress-applicable rule matches
rulesVec<Rule>Ordered rules; first match wins per direction

Rule

Held by NetworkPolicy

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).
FieldTypeDescription
directionDirectionWhich evaluator considers this rule
destinationDestinationTarget filter
protocolsVec<Protocol>Set semantics; empty = any protocol
portsVec<PortRange>Set semantics; empty = any port
actionActionWhat to do on match
Convenience constructors build any-protocol, any-port rules:
MethodDescription
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

Action

Used by Rule · NetworkPolicy · default_egress()

ValueWire formatDescription
Allow"allow"Permit the traffic
Deny"deny"Drop the traffic silently

Direction

Used by Rule

ValueWire formatDescription
Egress"egress"Traffic leaving the sandbox
Ingress"ingress"Traffic entering the sandbox (via published ports)
Any"any"Rule applies in either direction

Destination

Held by Rule · committed by RuleDestinationBuilder

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

DestinationGroup

Held by Destination · committed by RuleBuilder group adders

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

Protocol

Held by Rule · set by RuleBuilder protocol setters

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

PortRange

Held by Rule · added by port() · port_range()

An inclusive port range.
FieldTypeDescription
startu16Start port (inclusive)
endu16End port (inclusive)
MethodDescription
PortRange::single(port)Match a single port
PortRange::range(start, end)Match an inclusive range
contains(port)Whether port falls within the range

DomainName

Held by Destination::Domain / DomainSuffix

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.
MethodDescription
as_str()Borrow the canonical string form
try_into_suffix()Validate for use as a DomainSuffix; single-label names (e.g. com) are rejected

Nameserver

Used by nameservers()

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

InterfaceOverrides

Used by interface()

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.
FieldTypeDescription
macOption<[u8; 6]>Guest MAC address. Default: derived from slot
mtuOption<u16>Interface MTU. Default: 1500
ipv4_addressOption<Ipv4Addr>Guest IPv4 address. Default: derived from slot within ipv4_pool
ipv4_poolOption<Ipv4Network>IPv4 pool guest subnets are derived from. Default: 172.16.0.0/12
ipv6_addressOption<Ipv6Addr>Guest IPv6 address. Default: derived from slot within ipv6_pool
ipv6_poolOption<Ipv6Network>IPv6 pool guest prefixes are derived from. Default: fd42:6d73:62::/48

BuildError

Returned by NetworkPolicyBuilder::build() · wrapped by NetworkBuilder

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

ViolationAction

Built by ViolationActionBuilder · used by on_secret_violation()

Action taken when a secret placeholder is sent to a disallowed host. Also documented on the Secrets page, where it pairs with SecretBuilder.
ValueWire formatDescription
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