Documentation Index
Fetch the complete documentation index at: https://docs.microsandbox.dev/llms.txt
Use this file to discover all available pages before exploring further.
See Networking for conceptual overview and TLS Interception for TLS proxy details.
NetworkPolicy
A list of rules plus two per-direction defaults, evaluated first-match-wins. Use a static factory for a preset, build a literal, or chain through NetworkPolicy.builder() for the fluent builder:
import { NetworkPolicy, Rule, Destination } from "microsandbox";
// Custom policy literal
const custom = {
defaultEgress: "deny",
defaultIngress: "allow",
rules: [
Rule.allowEgress(Destination.domain("api.openai.com")),
Rule.denyEgress(Destination.group("metadata")),
],
};
// Or via the builder
const built = NetworkPolicy.builder()
.defaultDeny()
.egress((e) => e.tcp().port(443).allowPublic())
.build();
Pass any of these to NetworkBuilder.policy(...).
Rule order matters
The first matching rule wins, so a broad rule placed before a narrow one swallows it:
const policy = {
defaultEgress: "deny",
defaultIngress: "allow",
rules: [
Rule.allowEgress(Destination.cidr("10.0.0.0/8")), // matches everything in 10.x
Rule.denyEgress(Destination.cidr("10.0.0.5/32")), // never reached
],
};
Put specific rules before general ones.
Shadow detection
NetworkPolicyBuilder.build() walks the rules and warns when a rule is fully covered by an earlier one in the same direction. Only ip, cidr, and group destinations are checked; domain coverage depends on runtime DNS and is skipped. Builds still succeed; the warning surfaces as a host-side tracing::warn! from the rust core:
WARN rule #1 (Egress Cidr(10.0.0.5/32) Deny) is shadowed by rule #0 (Egress Cidr(10.0.0.0/8) Allow); to narrow, place the more specific rule first
Policy literals constructed via Rule.allowEgress(...) etc. do not run through the builder and skip this check.
State accumulation
State setters (.tcp(), .port(), etc.) inside a RuleBuilder callback carry into every rule-adder that follows. State is not reset between adders:
NetworkPolicy.builder()
.egress((r) => r
.tcp().port(443).allowPublic() // rule 1: egress, TCP, 443, allow Public
.udp().allowPrivate() // rule 2: egress, [TCP, UDP], 443, allow Private
)
.build();
Use separate .rule() / .egress() callbacks for rules that need different state.
NetworkPolicy.allowAll()
static allowAll(): NetworkPolicy
Unrestricted network access, including to private addresses and the host machine.
NetworkPolicy.builder()
static builder(): NetworkPolicyBuilder
Start a fluent NetworkPolicyBuilder. Equivalent to new NetworkPolicyBuilder().
NetworkPolicy.none()
static none(): NetworkPolicy
Deny all traffic. The guest is fully offline. exec and fs still work since they use the host-guest channel, not the network.
NetworkPolicy.nonLocal()
static nonLocal(): NetworkPolicy
Allow egress to public + private (LAN) destinations; ingress allowed by default.
NetworkPolicy.publicOnly()
static publicOnly(): NetworkPolicy
Block private address ranges and cloud metadata endpoints. Allow everything else. This is the default policy.
NetworkPolicyBuilder
Fluent builder for NetworkPolicy. The closure passed to .rule() / .egress() / .ingress() / .any() receives a RuleBuilder; state setters and rule-adders chain freely. The first parse / validation failure surfaces from .build().
import { NetworkPolicy } from "microsandbox";
const policy = NetworkPolicy.builder()
.defaultDeny()
.egress((e) => e.tcp().port(443).allowPublic().allowPrivate())
.rule((r) => r.any().deny((d) => d.ip("198.51.100.5")))
.build();
any()
any(configure: (r: RuleBuilder) => RuleBuilder): this
Sugar for rule() with direction pre-set to Any. Rules committed inside apply in both directions.
build()
Materialize the accumulated state into a NetworkPolicy. Lazily parses every recorded .ip() / .cidr() / .domain() / .domainSuffix() input, validates direction-set and ICMP-egress-only invariants, and emits a host-side warning for each shadowed rule pair.
defaultAllow()
Set both defaultEgress and defaultIngress to "allow".
defaultDeny()
Set both defaultEgress and defaultIngress to "deny".
defaultEgress()
defaultEgress(action: "allow" | "deny"): this
Per-direction override for the egress default action.
Parameters
| Name | Type | Description |
|---|
| action | "allow" | "deny" | Default action for egress |
defaultIngress()
defaultIngress(action: "allow" | "deny"): this
Per-direction override for the ingress default action.
Parameters
| Name | Type | Description |
|---|
| action | "allow" | "deny" | Default action for ingress |
egress()
egress(configure: (r: RuleBuilder) => RuleBuilder): this
Sugar for rule() with direction pre-set to Egress.
ingress()
ingress(configure: (r: RuleBuilder) => RuleBuilder): this
Sugar for rule() with direction pre-set to Ingress.
rule()
rule(configure: (r: RuleBuilder) => RuleBuilder): this
Open a multi-rule batch closure. Direction must be set inside via .egress(), .ingress(), or .any() before any rule-adder.
RuleBuilder
Per-rule-batch builder. Lives only inside the callback passed to .rule() / .egress() / .ingress() / .any() on a NetworkPolicyBuilder. State setters and rule-adders interleave freely; state accumulates eagerly across the callback (see State accumulation).
Direction setters
Last-write-wins. ICMP rule-adders are egress-only at build time.
any()
Set direction to Any for subsequent rule-adders. Rules committed after this apply in both directions.
egress()
Set direction to Egress for subsequent rule-adders.
ingress()
Set direction to Ingress for subsequent rule-adders.
Protocol setters
Protocols accumulate as a set; duplicates dedupe.
icmpv4()
Add Icmpv4 to the protocols set. Egress-only; an ICMP rule on an Ingress or Any direction fails build.
icmpv6()
Add Icmpv6 to the protocols set. Egress-only; same rules as icmpv4().
tcp()
Add Tcp to the protocols set.
udp()
Add Udp to the protocols set.
Port setters
Ports accumulate as a set; duplicates dedupe. Always guest-side (egress destination port / ingress listening port).
port()
Add a single port to the ports set.
Parameters
| Name | Type | Description |
|---|
| port | number | Port number 0..=65535 |
portRange()
portRange(lo: number, hi: number): this
Add an inclusive port range. lo > hi records an error surfaced at .build() time.
Parameters
| Name | Type | Description |
|---|
| lo | number | Lower bound (inclusive) |
| hi | number | Upper bound (inclusive) |
ports()
ports(ports: number[]): this
Add multiple single ports. Equivalent to calling port() once per element.
Group rule-adders
Each adder commits one rule using the current state and the named destination group.
allowHost()
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 allowLoopback().
allowLinkLocal()
Allow the LinkLocal group (169.254.0.0/16, fe80::/10). Excludes the metadata IP 169.254.169.254.
allowLoopback()
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 allowHost() instead. See the loopback-vs-host watch-out.
Allow the Metadata group (169.254.169.254). Dangerous on cloud hosts (exposes IAM credentials).
allowMulticast()
Allow the Multicast group (224.0.0.0/4, ff00::/8).
allowPrivate()
Allow the Private group (RFC1918 + ULA + CGN).
allowPublic()
Allow the Public group (complement of named categories: every IP not in any other group).
denyHost()
Deny the Host group.
denyLinkLocal()
Deny the LinkLocal group.
denyLoopback()
Deny the Loopback group.
Deny the Metadata group.
denyMulticast()
Deny the Multicast group.
denyPrivate()
Deny the Private group.
denyPublic()
Deny the Public group.
Domain rule-adders
Singular forms add one rule; plural forms add one rule per element.
allowDomain()
allowDomain(name: string): this
Add one Destination::Domain allow rule.
Parameters
| Name | Type | Description |
|---|
| name | string | Fully qualified domain name |
allowDomainSuffix()
allowDomainSuffix(suffix: string): this
Add one Destination::DomainSuffix allow rule. Matches the apex and any subdomain.
Parameters
| Name | Type | Description |
|---|
| suffix | string | Domain suffix |
allowDomainSuffixes()
allowDomainSuffixes(suffixes: string[]): this
Add one Destination::DomainSuffix allow rule per suffix.
allowDomains()
allowDomains(names: string[]): this
Add one Destination::Domain allow rule per name.
denyDomain()
denyDomain(name: string): this
Add one Destination::Domain deny rule.
Parameters
| Name | Type | Description |
|---|
| name | string | Fully qualified domain name |
denyDomainSuffix()
denyDomainSuffix(suffix: string): this
Add one Destination::DomainSuffix deny rule. Matches the apex and any subdomain.
Parameters
| Name | Type | Description |
|---|
| suffix | string | Domain suffix |
denyDomainSuffixes()
denyDomainSuffixes(suffixes: string[]): this
Add one Destination::DomainSuffix deny rule per suffix.
denyDomains()
denyDomains(names: string[]): this
Add one Destination::Domain deny rule per name.
Composite rule-adders
allowLocal()
Add three allow rules atomically: Loopback + LinkLocal + Host. Each uses the callback’s current state. Metadata is intentionally not included; opt in via allowMeta() separately.
denyLocal()
Add three deny rules atomically: Loopback + LinkLocal + Host. Metadata is intentionally not included.
Explicit-destination rule-adders
.allow() / .deny() open a RuleDestinationBuilder callback. Exactly one destination call commits the rule.
NetworkPolicy.builder()
.egress((r) => r
.tcp().port(443).allow((d) => d.domain("api.example.com"))
.deny((d) => d.cidr("198.51.100.0/24"))
)
.build();
allow()
allow(configure: (d: RuleDestinationBuilder) => RuleDestinationBuilder): this
Begin an explicit-destination rule with action Allow.
deny()
deny(configure: (d: RuleDestinationBuilder) => RuleDestinationBuilder): this
Begin an explicit-destination rule with action Deny.
Rule
Factory for individual policy rules. Each method returns a single Rule value.
Rule.allowAny()
static allowAny(destination: Destination): Rule
Allow rule with direction any (matches in either direction).
Parameters
| Name | Type | Description |
|---|
| destination | Destination | Target filter |
Rule.allowEgress()
static allowEgress(destination: Destination): Rule
Allow rule with direction egress.
Parameters
| Name | Type | Description |
|---|
| destination | Destination | Target filter |
Rule.allowIngress()
static allowIngress(destination: Destination): Rule
Allow rule with direction ingress.
Parameters
| Name | Type | Description |
|---|
| destination | Destination | Target filter |
Rule.denyAny()
static denyAny(destination: Destination): Rule
Deny rule with direction any (matches in either direction).
Parameters
| Name | Type | Description |
|---|
| destination | Destination | Target filter |
Rule.denyEgress()
static denyEgress(destination: Destination): Rule
Deny rule with direction egress.
Parameters
| Name | Type | Description |
|---|
| destination | Destination | Target filter |
Rule.denyIngress()
static denyIngress(destination: Destination): Rule
Deny rule with direction ingress.
Parameters
| Name | Type | Description |
|---|
| destination | Destination | Target filter |
Rule.allowDns()
Allow plain DNS (UDP/53 and TCP/53) to the sandbox gateway, i.e. the in-process DNS forwarder. The standard one-liner for opening DNS under a deny-by-default policy. See DNS as egress for the underlying semantics.
DoT (TCP/853) is intentionally not included; add an explicit Group::Host tcp/853 allow rule if needed (and pair with TLS interception).
Destination
Factory for rule destinations.
Destination.any()
static any(): Destination
Match any destination.
Destination.cidr()
static cidr(cidr: string): Destination
Match an IP range.
Parameters
| Name | Type | Description |
|---|
| cidr | string | CIDR notation (e.g. "10.0.0.0/8") |
Destination.domain()
static domain(domain: string): Destination
Match an exact domain.
Parameters
| Name | Type | Description |
|---|
| domain | string | Fully qualified domain name |
Destination.domainSuffix()
static domainSuffix(suffix: string): Destination
Match the apex domain and every subdomain.
Parameters
| Name | Type | Description |
|---|
| suffix | string | Domain suffix |
Destination.group()
static group(group: DestinationGroup): Destination
Match a predefined address group.
Parameters
| Name | Type | Description |
|---|
| group | DestinationGroup | Group keyword |
PortRange
Factory for port ranges.
PortRange.range()
static range(start: number, end: number): PortRange
Match an inclusive port range.
Parameters
| Name | Type | Description |
|---|
| start | number | Lower bound (inclusive) |
| end | number | Upper bound (inclusive) |
PortRange.single()
static single(port: number): PortRange
Match a single port. start and end are set to the same value.
Parameters
| Name | Type | Description |
|---|
| port | number | Port number |
NetworkBuilder
Returned to the callback you pass to SandboxBuilder.network(...). Every setter returns the same builder.
denyDomainSuffixes()
denyDomainSuffixes(...suffixes: string[]): this
Deny egress to all subdomains of these suffixes. Same enforcement layers as denyDomains().
Parameters
| Name | Type | Description |
|---|
| suffixes | string[] | Domain suffixes |
denyDomains()
denyDomains(...names: string[]): this
Deny egress to these exact domains. Each entry adds a deny Domain("...") policy rule that fires at DNS resolution (REFUSED), TLS first-flight (SNI), and TCP egress (cache fallback). Prepended onto the policy.
Parameters
| Name | Type | Description |
|---|
| names | string[] | Fully qualified domain names |
dns()
dns(f: (d: DnsBuilder) => DnsBuilder): this
Configure DNS interception. See DnsBuilder.
enabled()
enabled(b: boolean): this
Enable or disable networking entirely.
Parameters
| Name | Type | Description |
|---|
| b | boolean | When false, no network interface is created |
maxConnections()
maxConnections(n: number): this
Limit the maximum number of concurrent network connections from the sandbox.
onSecretViolation()
onSecretViolation(action: ViolationAction): this
Set the action taken when a secret reaches a disallowed host. See ViolationAction.
policy()
policy(policy: NetworkPolicy): this
Set the policy. Use a NetworkPolicy factory or a literal.
port()
port(host: number, guest: number): this
Publish a TCP port from the guest to the host.
Parameters
| Name | Type | Description |
|---|
| host | number | Port on the host |
| guest | number | Port inside the sandbox |
portUdp()
portUdp(host: number, guest: number): this
Publish a UDP port from the guest to the host.
Parameters
| Name | Type | Description |
|---|
| host | number | Port on the host |
| guest | number | Port inside the sandbox |
secret()
secret(f: (s: SecretBuilder) => SecretBuilder): this
Add a secret. See SecretBuilder.
secretEnv()
secretEnv(envVar: string, value: string, placeholder: string, allowedHost: string): this
Four-arg explicit-placeholder shorthand for adding a secret without opening a builder callback.
Parameters
| Name | Type | Description |
|---|
| envVar | string | Environment variable name |
| value | string | Real secret value |
| placeholder | string | Placeholder string surfaced to the guest |
| allowedHost | string | Single hostname allowed to receive the real value |
tls()
tls(f: (t: TlsBuilder) => TlsBuilder): this
Configure TLS interception. See TlsBuilder.
trustHostCAs()
trustHostCAs(enabled: boolean): this
Whether to ship the host’s trusted root CAs into the guest at boot. Default: false. Opt in for corporate MITM proxies (Cloudflare Warp Zero Trust, Zscaler, Netskope, etc.) whose gateway CA is installed on the host but unknown to the guest’s stock Mozilla bundle.
DnsBuilder
Builder for DNS interception settings. Used in NetworkBuilder.dns(d => ...). Owns rebind protection, nameserver pinning, and the per-query timeout.
nameservers()
nameservers(servers: string[]): this
Override upstream nameservers. Replaces any previously-set nameservers.
Parameters
| Name | Type | Description |
|---|
| servers | string[] | Each entry is IP, IP:PORT, HOST, or HOST:PORT |
queryTimeoutMs()
queryTimeoutMs(ms: number): this
Per-DNS-query timeout in milliseconds.
rebindProtection()
rebindProtection(enabled: boolean): this
Toggle DNS rebinding protection. When enabled, DNS responses resolving to private IPs are blocked.
TlsBuilder
Builder for TLS interception settings. Used in NetworkBuilder.tls(t => ...).
blockQuic()
blockQuic(block: boolean): this
Block QUIC on intercepted ports, forcing TCP/TLS fallback.
bypass()
bypass(pattern: string): this
Skip TLS interception for hosts matching this glob (e.g. "*.internal.corp"). Use for domains with certificate pinning.
Parameters
| Name | Type | Description |
|---|
| pattern | string | Glob pattern |
interceptCaCert()
interceptCaCert(path: string): this
Path to a PEM file used as the intercepting CA’s certificate.
interceptCaKey()
interceptCaKey(path: string): this
Path to a PEM file used as the intercepting CA’s private key.
interceptedPorts()
interceptedPorts(ports: number[]): this
TCP ports where interception is active. Default: [443].
upstreamCaCert()
upstreamCaCert(path: string): this
Path to a PEM file with extra root CAs the proxy should trust.
verifyUpstream()
verifyUpstream(verify: boolean): this
Verify upstream server certificates. Default true. Set to false only for self-signed servers.
Types
NetworkConfig
Built network configuration produced by NetworkBuilder.build().
| Field | Type | Description |
|---|
| enabled | boolean | Master enable flag |
| ports | readonly PublishedPort[] | Port publishings |
| policy | NetworkPolicy | null | Active policy |
| dns | DnsConfig | null | DNS interception |
| tls | TlsConfig | null | TLS interception |
| secrets | readonly SecretEntry[] | Secret entries |
| secretViolation | ViolationAction | null | Action on disallowed secret use |
| maxConnections | number | null | Maximum concurrent connections |
| trustHostCAs | boolean | Ship host CAs into the guest |
NetworkPolicy
interface NetworkPolicy {
readonly defaultEgress: Action;
readonly defaultIngress: Action;
readonly rules: readonly Rule[];
}
Rule
interface Rule {
readonly direction: Direction;
readonly destination: Destination;
readonly protocols: readonly Protocol[]; // empty = any
readonly ports: readonly PortRange[]; // empty = any
readonly action: Action;
}
Destination
type Destination =
| { kind: "any" }
| { kind: "cidr"; cidr: string }
| { kind: "domain"; domain: string }
| { kind: "domainSuffix"; suffix: string }
| { kind: "group"; group: DestinationGroup };
Action
type Action = "allow" | "deny";
Direction
type Direction = "egress" | "ingress" | "any";
Protocol
type Protocol = "tcp" | "udp" | "icmpv4" | "icmpv6";
DestinationGroup
type DestinationGroup =
| "public"
| "loopback"
| "private"
| "link-local"
| "metadata"
| "multicast"
| "host";
| Value | Description |
|---|
'public' | Public internet (everything not in another group) |
'loopback' | Guest’s own 127.0.0.0/8 / ::1 |
'private' | RFC1918 LAN ranges |
'link-local' | 169.254.0.0/16 / fe80::/10 |
'metadata' | Cloud metadata endpoints (169.254.169.254, fd00:ec2::254) |
'multicast' | Multicast addresses |
'host' | The host machine, reached via host.microsandbox.internal |
PortRange
interface PortRange {
readonly start: number;
readonly end: number;
}
RuleDestinationBuilder
Returned by RuleBuilder.allow(d => ...) / .deny(d => ...). Exactly one destination call commits the rule; dropping without a destination call silently does nothing.
| Method | Description |
|---|
.ip(ip) | Commit with Destination::Cidr of the IP as /32 or /128 |
.cidr(cidr) | Commit with Destination::Cidr |
.domain(domain) | Commit with Destination::Domain |
.domainSuffix(suffix) | Commit with Destination::DomainSuffix |
.group(group) | Commit with Destination::Group. group is a DestinationGroup string |
.any() | Commit with Destination::Any |
DnsConfig
| Field | Type | Description |
|---|
| nameservers | readonly string[] | Upstream nameservers |
| rebindProtection | boolean | null | DNS rebinding protection toggle |
| queryTimeoutMs | number | null | Per-query timeout |
TlsConfig
| Field | Type | Description |
|---|
| bypass | readonly string[] | Bypass globs (e.g. "*.googleapis.com") |
| verifyUpstream | boolean | null | Verify upstream certs |
| interceptedPorts | readonly number[] | Intercepted TCP ports |
| blockQuic | boolean | null | Block QUIC on intercepted ports |
| upstreamCaCertPaths | readonly string[] | Extra trust roots for upstream verification |
| interceptCaCertPath | string | null | Custom intercept CA cert (PEM) |
| interceptCaKeyPath | string | null | Custom intercept CA key (PEM) |
PublishedPort
interface PublishedPort {
readonly hostPort: number;
readonly guestPort: number;
readonly protocol: "tcp" | "udp";
}