docs/sdk/python/networking.mdx
Configure sandbox networking. See Networking for usage and policy concepts.
Sandbox network configuration.
Network(
policy: 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,
)
NetworkPolicy \| None · Default: None
Concrete network policy
Mapping[int, int] \| Sequence[PortBinding] · Default: {}
Port mappings from host to guest. Mapping form binds TCP to 127.0.0.1; PortBinding can set an explicit bind address or UDP
tuple[str, ...] · Default: ()
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
tuple[str, ...] · Default: ()
Deny egress to all subdomains of these suffixes. Adds deny DomainSuffix("...") rules; same enforcement layers as deny_domains
DnsConfig \| None · Default: None
DNS interception configuration
TlsConfig \| None · Default: None
TLS interception configuration
str \| None · Default: None
IPv4 pool used for per-sandbox /30 guest subnets. Defaults to 172.16.0.0/12
str \| None · Default: None
IPv6 pool used for per-sandbox /64 guest prefixes. Defaults to fd42:6d73:62::/48
int \| None · Default: None
Maximum concurrent connections
NetworkRateLimiter \| None · Default: None
Local ingress and egress rate limits. Cloud network configuration does not expose this field
ViolationAction\|ViolationPolicy · Default: BLOCK_AND_LOG
Sandbox-wide action when a secret placeholder reaches a disallowed host
@classmethod
def none() -> Network
sb = await Sandbox.create("offline", image="python", network=Network.none())
Deny all traffic in both directions. The network interface remains present; exec and fs still work since they use the host-guest channel, not the network.
@classmethod
def from_profiles(*profiles: NetworkProfile) -> Network
Build a deny-by-default network configuration from PUBLIC, PRIVATE, and HOST profiles. Duplicate profiles are ignored, generated rules use canonical order, and gateway DNS is added automatically for every non-empty profile set.
from microsandbox import Network, NetworkProfile
network = Network.from_profiles(NetworkProfile.PUBLIC, NetworkProfile.PRIVATE)
@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>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,
)
What to do when this rule matches
Direction · Default: EGRESS
Which evaluator considers this rule. Direction.ANY matches in either direction
str \| NetworkDestination \| None · Default: 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 \| None · Default: None
Protocol filter
int \| str \| None · Default: 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.
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
from microsandbox import Destination, Protocol, Rule
r = Rule.allow(protocol=Protocol.TCP, port=443, destination=Destination.domain("api.example.com"))
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>@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]
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),
),
),
)
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).
@classmethod
def deny_dns() -> tuple[Rule, Rule]
Deny gateway UDP/53 and TCP/53. Place these rules before profile-generated rules to override their automatic DNS access.
<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">tuple[Rule, Rule]</a></div> <div className="msb-param-desc"><code>(udp_rule, tcp_rule)</code> for <code>DestGroup.HOST</code> on port 53.</div> </div> </div>Factory for typed NetworkDestination values.
@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) -> NetworkDestination
Match a well-known DestGroup address group.
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,
)
int
Port on the host
int
Port inside the sandbox
str · Default: "127.0.0.1"
Host address to bind. Use 0.0.0.0 for all IPv4 interfaces
PortProtocol · Default: TCP
Published port protocol
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>Ordered rules with per-direction defaults.
NetworkPolicy(
default_egress: Action = Action.DENY,
default_ingress: Action = Action.ALLOW,
rules: tuple[Rule, ...] = (),
)
Action · Default: DENY
Action when no egress-applicable rule matches
Action · Default: ALLOW
Action when no ingress-applicable rule matches
tuple[Rule, ...] · Default: ()
Rules evaluated first-match-wins per direction
Class methods none() and allow_all() construct terminal whole policies. from_profiles(profiles) composes NetworkProfile values with canonical ordering and automatic gateway DNS.
NetworkPolicy.none()
Deny all ingress and egress traffic
<p className="msb-label">Returns</p>NetworkPolicy
NetworkPolicy.allow_all()
Allow all ingress and egress traffic
<p className="msb-label">Returns</p>NetworkPolicy
NetworkPolicy.from_profiles(profiles)
Compose the selected canonical network profiles
<p className="msb-label">Returns</p>NetworkPolicy
| Member | Value | Description |
|---|---|---|
NetworkProfile.PUBLIC | "public" | Public internet addresses |
NetworkProfile.PRIVATE | "private" | Private/LAN ranges |
NetworkProfile.HOST | "host" | Sandbox host gateway addresses |
Frozen dataclass produced by Destination helpers.
NetworkDestination(
kind: NetworkDestinationKind,
value: str | None = None,
)
| Field | Type | Default | Description |
|---|---|---|---|
| kind | NetworkDestinationKind | - | Destination variant |
| value | str | None | None | Variant value, omitted for Destination.any() |
Network destination variant.
| Member | Value | Description |
|---|---|---|
NetworkDestinationKind.ANY | "any" | Any destination |
NetworkDestinationKind.IP | "ip" | One IP address |
NetworkDestinationKind.CIDR | "cidr" | One CIDR network |
NetworkDestinationKind.DOMAIN | "domain" | One exact domain |
NetworkDestinationKind.DOMAIN_SUFFIX | "domain_suffix" | A domain suffix and its subdomains |
NetworkDestinationKind.GROUP | "group" | A well-known DestGroup |
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,
upstream_ca_certs: tuple[str, ...] = (),
scoped_upstream_ca_certs: tuple[ScopedUpstreamCACert, ...] = (),
scoped_verify_upstream: tuple[ScopedVerifyUpstream, ...] = (),
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 |
| upstream_ca_certs | tuple[str, ...] | () | Paths to additional CA bundles trusted for every upstream host |
| scoped_upstream_ca_certs | tuple[ScopedUpstreamCACert, ...] | () | Host-pattern-scoped CA bundles trusted only for matching upstream hosts |
| scoped_verify_upstream | tuple[ScopedVerifyUpstream, ...] | () | Host-pattern-scoped upstream certificate verification overrides |
| 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 |
A CA bundle trusted only for upstream hosts matching a pattern.
ScopedUpstreamCACert(
pattern: str,
path: str,
)
| Field | Type | Description |
|---|---|---|
| pattern | str | Exact host or *.suffix wildcard |
| path | str | CA bundle path trusted for matching upstream hosts |
A per-host override for upstream certificate verification.
ScopedVerifyUpstream(
pattern: str,
verify: bool,
)
| Field | Type | Description |
|---|---|---|
| pattern | str | Exact host or *.suffix wildcard |
| verify | bool | Whether to verify certificates for matching upstream hosts |
Groups local network limits by traffic direction. An omitted direction is unlimited.
| Field | Type | Description |
|---|---|---|
| egress | RateLimiter | None | Guest-to-runtime rate limiter |
| ingress | RateLimiter | None | Runtime-to-guest rate limiter |
Limits bandwidth and packet rate for one traffic direction.
| Field | Type | Description |
|---|---|---|
| bandwidth | TokenBucket | None | Byte budget; one token per byte of frame data |
| ops | TokenBucket | None | Packet budget; one token per network frame |
Token-bucket configuration for one rate-limiter dimension.
| Field | Type | Description |
|---|---|---|
| size | int | Bucket capacity in bytes or frames. Must be greater than zero |
| refill_time_ms | int | Time to refill size tokens. Must be greater than zero |
| one_time_burst | int | Extra startup-only tokens. Default: 0 |
Policy action.
| Member | Value | Description |
|---|---|---|
Action.ALLOW | "allow" | Permit the traffic |
Action.DENY | "deny" | Drop the traffic silently |
String enum for traffic direction.
| Member | Value | Description |
|---|---|---|
Direction.EGRESS | "egress" | Traffic leaving the sandbox |
Direction.INGRESS | "ingress" | Traffic entering the sandbox (via published ports) |
Direction.ANY | "any" | Rule applies in either direction |
String enum for network protocols in policy rules.
| Member | Value | Description |
|---|---|---|
Protocol.TCP | "tcp" | TCP traffic |
Protocol.UDP | "udp" | UDP traffic |
Protocol.ICMPV4 | "icmpv4" | ICMPv4 traffic |
Protocol.ICMPV6 | "icmpv6" | ICMPv6 traffic |
String enum for port-level protocol selection.
| Member | Value | Description |
|---|---|---|
PortProtocol.TCP | "tcp" | TCP port |
PortProtocol.UDP | "udp" | UDP port |
String enum for well-known destination groups used in Destination.group() or string-shorthand Rule.destination.
| Member | Value | Description |
|---|---|---|
DestGroup.PUBLIC | "public" | Complement of the named categories: every address not in any other group |
DestGroup.PRIVATE | "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) |
DestGroup.LOOPBACK | "loopback" | Loopback addresses (127.0.0.0/8, ::1); the guest's own loopback, not the host. See the loopback-vs-host watch-out |
DestGroup.LINK_LOCAL | "link-local" | Link-local addresses (169.254.0.0/16, fe80::/10) excluding metadata |
DestGroup.METADATA | "metadata" | Cloud metadata endpoints (169.254.169.254) |
DestGroup.MULTICAST | "multicast" | Multicast addresses (224.0.0.0/4, ff00::/8) |
DestGroup.HOST | "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" |