docs/sdk/python/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. Pass a Network as the network= kwarg to Sandbox.create(). See Networking for the conceptual overview and TLS Interception for proxy details.
from microsandbox import (
Action,
DestGroup,
Destination,
Network,
NetworkPolicy,
PortBinding,
Protocol,
Rule,
Sandbox,
)
policy = NetworkPolicy( # 1. compose a policy
default_egress=Action.DENY,
rules=(
*Rule.allow_dns(),
Rule.allow(
protocol=Protocol.TCP,
port=443,
destination=Destination.group(DestGroup.PUBLIC),
),
),
)
sb = await Sandbox.create( # 2. wire it into the sandbox
"api",
image="python",
network=Network(
policy=policy,
ports=(PortBinding.tcp(8080, 80),),
),
)
The default policy denies egress except for an implicit allow-public rule, and allows ingress with no rules. See the defaults rationale for the asymmetry. Network, Rule, Destination, and PortBinding are all frozen dataclasses re-exported from microsandbox, each carrying class-method or static-method constructors for the common cases.
Network is a frozen dataclass. The presets below return a Network for the common policy shapes; construct one directly for custom policies, ports, DNS, or TLS.
@classmethod
def none() -> Network
Deny all traffic. No network interface is created; the guest is fully offline. exec and fs still work since they use the host-guest channel, not the network.
sb = await Sandbox.create("offline", image="python", network=Network.none())
@classmethod
def public_only() -> Network
Block private address ranges and cloud metadata endpoints. Allow everything else. This is the default policy.
<p className="msb-label">Returns</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><a className="msb-type" href="#network">Network</a></div> <div className="msb-param-desc">Public-only network configuration.</div> </div> </div>@classmethod
def allow_all() -> Network
Unrestricted network access, including to private addresses and the host machine.
<p className="msb-label">Returns</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><a className="msb-type" href="#network">Network</a></div> <div className="msb-param-desc">Unrestricted network configuration.</div> </div> </div>A NetworkPolicy is an ordered list of Rule values plus two per-direction defaults, evaluated first-match-wins per direction. The class methods below build rules; assemble them into NetworkPolicy(rules=(...)) and pass it as Network(policy=...).
from microsandbox import Action, Destination, NetworkPolicy, Protocol, Rule
policy = NetworkPolicy(
default_egress=Action.DENY,
default_ingress=Action.ALLOW,
rules=(
Rule.allow(protocol=Protocol.TCP, port=443, destination=Destination.ip("1.1.1.1")),
Rule.deny(destination=Destination.domain("api.example.com")),
),
)
The first matching rule wins, so a broad rule placed before a narrow one swallows it:
policy = NetworkPolicy(
default_egress=Action.DENY,
default_ingress=Action.ALLOW,
rules=(
Rule.allow(destination="10.0.0.0/8"), # matches everything in 10.x
Rule.deny(destination="10.0.0.5"), # never reached
),
)
Put specific rules before general ones.
@classmethod
def allow(
*,
direction: Direction = Direction.EGRESS,
protocol: Protocol | None = None,
port: int | str | None = None,
destination: str | NetworkDestination | None = None,
) -> Rule
Create a rule that permits matching traffic. All filters are keyword-only.
<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>direction</code><a className="msb-type" href="#direction">Direction</a></div> <div className="msb-param-desc">Which evaluator considers the rule. Defaults to <code>EGRESS</code>.</div> </div> <div className="msb-param"> <div className="msb-param-key"><code>protocol</code><a className="msb-type" href="#protocol">Protocol | None</a></div> <div className="msb-param-desc">Protocol filter.</div> </div> <div className="msb-param"> <div className="msb-param-key"><code>port</code><span className="msb-type">int | str | None</span></div> <div className="msb-param-desc">Single port (<code>443</code>) or range (<code>"8000-9000"</code>).</div> </div> <div className="msb-param"> <div className="msb-param-key"><code>destination</code><a className="msb-type" href="#networkdestination">str | NetworkDestination | None</a></div> <div className="msb-param-desc">Target filter. Prefer the typed <a className="msb-type" href="#destination">Destination</a> helpers; string shorthand is also accepted.</div> </div> </div> <p className="msb-label">Returns</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><a className="msb-type" href="#rule">Rule</a></div> <div className="msb-param-desc">An allow rule.</div> </div> </div> <Accordion title="Example">from microsandbox import Destination, Protocol, Rule
r = Rule.allow(protocol=Protocol.TCP, port=443, destination=Destination.domain("api.example.com"))
@classmethod
def deny(
*,
direction: Direction = Direction.EGRESS,
protocol: Protocol | None = None,
port: int | str | None = None,
destination: str | NetworkDestination | None = None,
) -> Rule
Create a rule that blocks matching traffic. Same keyword-only filters as allow().
@classmethod
def allow_dns() -> tuple[Rule, Rule]
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.
Returns the pair (udp_rule, tcp_rule) since this SDK's Rule shape carries a single protocol; splat into NetworkPolicy.rules. DoT (TCP/853) is intentionally not included; add an explicit Rule.allow(destination=Destination.group(DestGroup.HOST), protocol=Protocol.TCP, port=853) if needed (and pair with TLS interception).
from microsandbox import Action, DestGroup, Destination, NetworkPolicy, Protocol, Rule
policy = NetworkPolicy(
default_egress=Action.DENY,
rules=(
*Rule.allow_dns(),
Rule.allow(
protocol=Protocol.TCP,
port=443,
destination=Destination.group(DestGroup.PUBLIC),
),
),
)
Factory namespace for typed network policy destinations. Typed destinations avoid string-shorthand ambiguity and match the TypeScript SDK shape. Each method returns a NetworkDestination.
from microsandbox import DestGroup, Destination
Destination.any()
Destination.ip("1.1.1.1")
Destination.cidr("10.0.0.0/8")
Destination.domain("api.example.com")
Destination.domain_suffix(".example.com")
Destination.group(DestGroup.LINK_LOCAL)
Bare IP strings such as "1.1.1.1" remain supported on Rule destinations and are parsed like Destination.ip("1.1.1.1"). Prefer the typed helper when the value is known to be an IP.
@staticmethod
def any() -> NetworkDestination
Match any destination.
@staticmethod
def ip(ip: str) -> NetworkDestination
Match an exact IPv4 or IPv6 address. Stored as /32 for IPv4 or /128 for IPv6.
@staticmethod
def cidr(cidr: str) -> NetworkDestination
Match a CIDR range.
<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>cidr</code><span className="msb-type">str</span></div> <div className="msb-param-desc">CIDR notation, e.g. <code>"10.0.0.0/8"</code>.</div> </div> </div>@staticmethod
def domain(domain: str) -> NetworkDestination
Match an exact domain. Domain strings are validated at sandbox creation; invalid names raise ValueError.
@staticmethod
def domain_suffix(suffix: str) -> NetworkDestination
Match the apex domain and all subdomains.
<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>suffix</code><span className="msb-type">str</span></div> <div className="msb-param-desc">Domain suffix, e.g. <code>".example.com"</code>.</div> </div> </div>@staticmethod
def group(group: DestGroup | str) -> NetworkDestination
Match a well-known DestGroup address group.
PortBinding is a frozen dataclass for published ports that need an explicit host bind address or UDP. Prefer the protocol-specific constructors over building one by hand.
PortBinding.tcp(8001, 8001, bind="0.0.0.0")
PortBinding.udp(5353, 5353, bind="0.0.0.0")
Pass them to Network(ports=(...)). A plain dict[int, int] is also accepted for the common case, binding TCP to 127.0.0.1.
@classmethod
def tcp(cls, host_port: int, guest_port: int, *, bind: str = "127.0.0.1") -> PortBinding
Publish a TCP port from the sandbox to the host.
<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>host_port</code><span className="msb-type">int</span></div> <div className="msb-param-desc">Port on the host.</div> </div> <div className="msb-param"> <div className="msb-param-key"><code>guest_port</code><span className="msb-type">int</span></div> <div className="msb-param-desc">Port inside the sandbox.</div> </div> <div className="msb-param"> <div className="msb-param-key"><code>bind</code><span className="msb-type">str</span></div> <div className="msb-param-desc">Host bind address. Defaults to <code>127.0.0.1</code>; use <code>0.0.0.0</code> for all IPv4 interfaces.</div> </div> </div> <p className="msb-label">Returns</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><a className="msb-type" href="#portbinding">PortBinding</a></div> <div className="msb-param-desc">A TCP port binding.</div> </div> </div>@classmethod
def udp(cls, host_port: int, guest_port: int, *, bind: str = "127.0.0.1") -> PortBinding
Publish a UDP port from the sandbox to the host.
<p className="msb-label">Parameters</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><code>host_port</code><span className="msb-type">int</span></div> <div className="msb-param-desc">Port on the host.</div> </div> <div className="msb-param"> <div className="msb-param-key"><code>guest_port</code><span className="msb-type">int</span></div> <div className="msb-param-desc">Port inside the sandbox.</div> </div> <div className="msb-param"> <div className="msb-param-key"><code>bind</code><span className="msb-type">str</span></div> <div className="msb-param-desc">Host bind address. Defaults to <code>127.0.0.1</code>.</div> </div> </div> <p className="msb-label">Returns</p> <div className="msb-params"> <div className="msb-param"> <div className="msb-param-key"><a className="msb-type" href="#portbinding">PortBinding</a></div> <div className="msb-param-desc">A UDP port binding.</div> </div> </div>Frozen dataclass for sandbox network configuration. Use a class-method preset for the common cases, or construct directly with custom options.
Network(
policy: str | NetworkPolicy | None = None,
ports: Mapping[int, int] | Sequence[PortBinding] = {},
deny_domains: tuple[str, ...] = (),
deny_domain_suffixes: tuple[str, ...] = (),
dns: DnsConfig | None = None,
tls: TlsConfig | None = None,
ipv4_pool: str | None = None,
ipv6_pool: str | None = None,
max_connections: int | None = None,
on_secret_violation: ViolationAction | ViolationPolicy = ViolationAction.BLOCK_AND_LOG,
)
| Field | Type | Default | Description |
|---|---|---|---|
| policy | str | NetworkPolicy | None | None | Preset name ("none", "public_only", "allow_all") or a custom NetworkPolicy |
| ports | Mapping[int, int] | Sequence[PortBinding] | {} | Port mappings from host to guest. Mapping form binds TCP to 127.0.0.1; PortBinding can set an explicit bind address or UDP |
| deny_domains | tuple[str, ...] | () | Deny egress to these exact domains. Each entry adds a deny Domain("...") policy rule that fires at DNS resolution (NXDOMAIN), TLS first-flight (SNI), and TCP egress (cache fallback). Prepended onto the policy so it takes precedence over later allow rules |
| deny_domain_suffixes | tuple[str, ...] | () | Deny egress to all subdomains of these suffixes. Adds deny DomainSuffix("...") rules; same enforcement layers as deny_domains |
| dns | DnsConfig | None | None | DNS interception configuration |
| tls | TlsConfig | None | None | TLS interception configuration |
| ipv4_pool | str | None | None | IPv4 pool used for per-sandbox /30 guest subnets. Defaults to 172.16.0.0/12 |
| ipv6_pool | str | None | None | IPv6 pool used for per-sandbox /64 guest prefixes. Defaults to fd42:6d73:62::/48 |
| max_connections | int | None | None | Maximum concurrent connections |
| on_secret_violation | ViolationAction|ViolationPolicy | BLOCK_AND_LOG | Sandbox-wide action when a secret placeholder reaches a disallowed host |
| Class method | Returns | Description |
|---|---|---|
| none() | Network | Deny all traffic, no interface |
| public_only() | Network | Public destinations only (default) |
| allow_all() | Network | Unrestricted access |
Frozen dataclass: an ordered list of Rule values plus two per-direction defaults, evaluated first-match-wins. The defaults are asymmetric to preserve today's behavior: egress falls through to deny (public_only reachability when paired with the implicit allow-public rule); ingress falls through to allow (unfiltered published-port behavior).
NetworkPolicy(
default_egress: Action = Action.DENY,
default_ingress: Action = Action.ALLOW,
rules: tuple[Rule, ...] = (),
)
| Field | Type | Default | Description |
|---|---|---|---|
| default_egress | Action | DENY | Action when no egress-applicable rule matches |
| default_ingress | Action | ALLOW | Action when no ingress-applicable rule matches |
| rules | tuple[Rule, ...] | () | Rules evaluated first-match-wins per direction |
Frozen dataclass for a single network policy rule. Prefer the Rule.allow() / Rule.deny() class methods over the positional constructor.
Rule(
action: Action,
direction: Direction = Direction.EGRESS,
destination: str | NetworkDestination | None = None,
protocol: Protocol | None = None,
port: int | str | None = None,
)
| Field | Type | Default | Description |
|---|---|---|---|
| action | Action | - | What to do when this rule matches |
| direction | Direction | EGRESS | Which evaluator considers this rule. Direction.ANY matches in either direction |
| destination | str | NetworkDestination | None | None | Target filter. Prefer typed Destination helpers; string shorthand also works (DestGroup values, exact IPs, domains, CIDR ranges, domain suffixes prefixed with ".", or "*" for any). Domain and suffix strings are validated at sandbox creation; invalid names raise ValueError |
| protocol | Protocol | None | None | Protocol filter |
| port | int | str | None | None | Single port (443) or range ("8000-9000") |
Ingress rules carrying ICMP protocols are rejected at sandbox creation; the host has no inbound ICMP path. Use Direction.EGRESS for ICMP allow/deny.
| Class method | Returns | Description |
|---|---|---|
| allow(...) | Rule | Permit matching traffic |
| deny(...) | Rule | Block matching traffic |
| allow_dns() | tuple[Rule, Rule] | Open plain DNS to the gateway |
Factory namespace (static methods) producing typed NetworkDestination values.
| Method | Returns | Description |
|---|---|---|
| any() | NetworkDestination | Match any destination |
| ip(ip) | NetworkDestination | Match an exact IPv4 or IPv6 address (/32 or /128) |
| cidr(cidr) | NetworkDestination | Match a CIDR range |
| domain(domain) | NetworkDestination | Match an exact domain |
| domain_suffix(suffix) | NetworkDestination | Match the apex domain and all subdomains |
| group(group) | NetworkDestination | Match a DestGroup value |
Frozen dataclass produced by Destination helpers.
NetworkDestination(
kind: Literal["any", "ip", "cidr", "domain", "domain_suffix", "group"],
value: str | None = None,
)
| Field | Type | Default | Description |
|---|---|---|---|
| kind | Literal["any", "ip", "cidr", "domain", "domain_suffix", "group"] | - | Destination variant |
| value | str | None | None | Variant value, omitted for Destination.any() |
Frozen dataclass for a published host-to-guest port with an optional host bind address. Prefer the PortBinding.tcp() / PortBinding.udp() class methods.
PortBinding(
host_port: int,
guest_port: int,
bind: str = "127.0.0.1",
protocol: PortProtocol = PortProtocol.TCP,
)
| Field | Type | Default | Description |
|---|---|---|---|
| host_port | int | - | Port on the host |
| guest_port | int | - | Port inside the sandbox |
| bind | str | "127.0.0.1" | Host address to bind. Use 0.0.0.0 for all IPv4 interfaces |
| protocol | PortProtocol | TCP | Published port protocol |
| Class method | Returns | Description |
|---|---|---|
| tcp(host_port, guest_port, *, bind) | PortBinding | A TCP port binding |
| udp(host_port, guest_port, *, bind) | PortBinding | A UDP port binding |
Frozen dataclass for DNS interception settings. The value type of Network.dns; import it from microsandbox.types.
DnsConfig(
rebind_protection: bool = True,
nameservers: tuple[str, ...] = (),
query_timeout_ms: int | None = None,
)
| Field | Type | Default | Description |
|---|---|---|---|
| rebind_protection | bool | True | Block DNS responses resolving to private IPs |
| nameservers | tuple[str, ...] | () | Nameservers (IP, IP:PORT, HOST, or HOST:PORT). Overrides the host's /etc/resolv.conf when set. Hostnames are resolved once at startup via the host's OS resolver |
| query_timeout_ms | int | None | None | Per-DNS-query timeout in milliseconds. Defaults to 5000 |
Frozen dataclass for TLS interception settings within Network.
TlsConfig(
bypass: tuple[str, ...] = (),
verify_upstream: bool = True,
intercepted_ports: tuple[int, ...] = (443,),
block_quic: bool = False,
ca_cert: str | None = None,
ca_key: str | None = None,
ca_cn: str | None = None,
)
| Field | Type | Default | Description |
|---|---|---|---|
| bypass | tuple[str, ...] | () | Domains to skip interception. Use for domains with certificate pinning |
| verify_upstream | bool | True | Verify upstream server certificates. Set to False only for self-signed servers |
| intercepted_ports | tuple[int, ...] | (443,) | TCP ports where TLS interception is active |
| block_quic | bool | False | Block QUIC/HTTP3 (UDP) on intercepted ports, forcing TCP/TLS fallback |
| ca_cert | str | None | None | Path to a custom interception CA certificate PEM file |
| ca_key | str | None | None | Path to a custom interception CA private key PEM file |
| ca_cn | str | None | None | Common name for the generated interception CA |
String enum (StrEnum) for policy actions, so the string values are accepted directly.
| Value | Description |
|---|---|
"allow" | Permit the traffic |
"deny" | Drop the traffic silently |
String enum for traffic direction.
| Value | Description |
|---|---|
"egress" | Traffic leaving the sandbox |
"ingress" | Traffic entering the sandbox (via published ports) |
"any" | Rule applies in either direction |
String enum for network protocols in policy rules.
| Value | Description |
|---|---|
"tcp" | TCP traffic |
"udp" | UDP traffic |
"icmpv4" | ICMPv4 traffic |
"icmpv6" | ICMPv6 traffic |
String enum for port-level protocol selection.
| Value | Description |
|---|---|
"tcp" | TCP port |
"udp" | UDP port |
String enum for well-known destination groups used in Destination.group() or string-shorthand Rule.destination.
| Value | Description |
|---|---|
"public" | Complement of the named categories: every address not in any other group |
"private" | Private/RFC 1918 addresses + ULA + CGN (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10, fc00::/7) |
"loopback" | Loopback addresses (127.0.0.0/8, ::1); the guest's own loopback, not the host. See the loopback-vs-host watch-out |
"link-local" | Link-local addresses (169.254.0.0/16, fe80::/10) excluding metadata |
"metadata" | Cloud metadata endpoints (169.254.169.254) |
"multicast" | Multicast addresses (224.0.0.0/4, ff00::/8) |
"host" | The host machine, reached via host.microsandbox.internal. This is the right group for "let the sandbox reach my host's localhost", not "loopback" |