---
title: Overview
description: Control network access and isolation
icon: "network-wired"
---

All network traffic from a sandbox flows through a host-controlled networking stack. From inside, the sandbox sees a normal network interface, but on the host side every packet is subject to policy before it goes anywhere. The sandbox's only path to the outside world is through this stack, so blocked traffic never leaves the VM.

## Defaults

Without any policy flags, sandboxes get **public-internet egress only**: routable destinations work, but private (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `100.64.0.0/10`, `fc00::/7`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), and the cloud metadata endpoint (`169.254.169.254`) are denied. Inbound traffic on published ports is unfiltered until you add an explicit ingress rule.

Sandbox addresses come from internal guest pools unless you provide your own. The default IPv4 pool is `172.16.0.0/12`; slot 0 gets `172.16.0.2` with gateway `172.16.0.1`. The default IPv6 pool is `fd42:6d73:62::/48`, a Unique Local Address (ULA) range; each sandbox gets one `/64` prefix with `::2` as the guest and `::1` as the gateway.

If either pool overlaps your host, VPN, VPC, or lab network, set a custom pool:

```bash
msb create python --name custom-net \
    --net-ipv4-pool 172.24.0.0/13 \
    --net-ipv6-pool fd7a:115c:a1e0::/48
```

To turn off networking entirely:

<CodeGroup>
```rust Rust
let sb = Sandbox::builder("isolated")
    .image("python")
    .network(|n| n.enabled(false))
    .create()
    .await?;
```

```typescript TypeScript
await using sb = await Sandbox.builder("isolated")
    .image("python")
    .disableNetwork()
    .create();
```

```python Python
from microsandbox import Network, Sandbox

sb = await Sandbox.create(
    "isolated",
    image="python",
    network=Network.none(),
)
```

```go Go
sb, err := m.CreateSandbox(ctx, "isolated",
    m.WithImage("python"),
    m.WithNetwork(m.NetworkPolicy.None()),
)
```

```bash CLI
msb create python --name isolated --no-net
```
</CodeGroup>

## Custom policies

A policy is a list of rules plus two direction-specific defaults:

```text
default_egress  : Allow | Deny     (action when no egress rule matches)
default_ingress : Allow | Deny     (action when no ingress rule matches)
rules           : [Rule, ...]      (first match wins)
```

Each rule carries its own direction (`Egress`, `Ingress`, or `Any`), a destination, an optional protocol set, an optional port set, and an action.

`--net-rule` takes a comma-separated list of tokens shaped as
`<action>[:<direction>]@<target>[:<proto>[:<ports>]]`. Direction defaults to `egress` when omitted. Repeating the flag concatenates tokens in argv order; first match wins.

<CodeGroup>
```rust Rust
use microsandbox::{NetworkPolicy, Sandbox};

let policy = NetworkPolicy::builder()
    .default_deny()
    .egress(|e| e.tcp().port(443).allow_public())
    .egress(|e| e.udp().tcp().port(53).allow_host())
    .build()?;

let sb = Sandbox::builder("secure-agent")
    .image("alpine")
    .network(|n| n.policy(policy))
    .create()
    .await?;
```

```typescript TypeScript
import { Destination, PortRange, Sandbox } from "microsandbox";

await using sb = await Sandbox.builder("secure-agent")
    .image("alpine")
    .network((n) => n.policy({
        defaultEgress: "deny",
        defaultIngress: "allow",
        rules: [
            {
                direction: "egress",
                destination: Destination.any(),
                protocols: ["tcp"],
                ports: [PortRange.single(443)],
                action: "allow",
            },
            {
                direction: "egress",
                destination: Destination.any(),
                protocols: ["udp"],
                ports: [PortRange.single(53)],
                action: "allow",
            },
        ],
    }))
    .create();
```

```python Python
from microsandbox import Sandbox

sb = await Sandbox.create(
    "secure-agent",
    image="alpine",
    network={
        "policy": {
            "default_egress": "deny",
            "default_ingress": "allow",
            "rules": [
                {"action": "allow", "direction": "egress", "protocol": "tcp", "port": "443"},
                {"action": "allow", "direction": "egress", "protocol": "udp", "port": "53"},
            ],
        },
    },
)
```

```go Go
sb, err := m.CreateSandbox(ctx, "secure-agent",
    m.WithImage("alpine"),
    m.WithNetwork(&m.NetworkConfig{
        DefaultEgress:  m.PolicyActionDeny,
        DefaultIngress: m.PolicyActionAllow,
        Rules: []m.PolicyRule{
            {
                Action:      m.PolicyActionAllow,
                Direction:   m.PolicyDirectionEgress,
                Destination: "*",
                Protocol:    m.PolicyProtocolTCP,
                Port:        "443",
            },
            {
                Action:      m.PolicyActionAllow,
                Direction:   m.PolicyDirectionEgress,
                Destination: "*",
                Protocol:    m.PolicyProtocolUDP,
                Port:        "53",
            },
        },
    }),
)
```

```bash CLI
# Public internet plus the host machine, deny everything else by default
msb create python --name agent \
    --net-rule "allow@public,allow@host"

# Allow public internet plus a specific private host on tcp/443
msb create alpine --name secure-agent \
    --net-rule "allow@10.0.5.10:tcp:443,allow@public" \
    --net-default-egress deny

# Block tcp/445 to private IPs while keeping everything else open
msb create alpine --name smb-blocked \
    --net-default-egress allow \
    --net-rule "deny@private:tcp:445"

# Publish a port and only accept ingress from RFC1918 sources
msb create python --name api \
    --port 8080:8080 \
    --net-rule "allow:ingress@private"
```
</CodeGroup>

Rules can target groups like `public`, `private`, and `host`, or specific IPs, CIDRs, domains, and port ranges. The full grammar lives in the [CLI reference](/cli/sandbox-commands#network-policy-flags), and SDK-specific shapes live in each networking reference.

For deny-by-default policies, remember that DNS is delivered through the sandbox gateway. The `host` group is usually the right target for DNS egress; see [DNS-as-egress](/networking/dns#dns-as-egress) for the full breakdown.

## Port mapping

Expose ports from the sandbox to the host so services running inside the VM are reachable from your machine.
Published ports bind to `127.0.0.1` by default. Specify a bind address, such as `0.0.0.0`, when you intentionally want a wider listener.

<CodeGroup>
```rust Rust
let sb = Sandbox::builder("api")
    .image("python")
    .port(8080, 80)
    .port_bind("0.0.0.0".parse()?, 8001, 8001)
    .port_udp(5353, 5353)
    .create()
    .await?;
```

```typescript TypeScript
await using sb = await Sandbox.builder("api")
    .image("python")
    .port(8080, 80)
    .portBind("0.0.0.0", 8001, 8001)
    .portUdp(5353, 5353)
    .create();
```

```python Python
from microsandbox import PortBinding

sb = await Sandbox.create(
    "api",
    image="python",
    ports=[PortBinding.tcp(8001, 8001, bind="0.0.0.0")],
)
```

```go Go
sb, err := m.CreateSandbox(ctx, "api",
    m.WithImage("python"),
    m.WithPorts(map[uint16]uint16{8080: 80}),
    m.WithPortBindings(m.PortBinding{Bind: "0.0.0.0", HostPort: 8001, GuestPort: 8001}),
    m.WithPortsUDP(map[uint16]uint16{5353: 5353}),
)
```

```bash CLI
msb create python --name api -p 8080:80
msb create python --name api-public -p 0.0.0.0:8001:8001
```

</CodeGroup>

## Reaching the host

From inside the sandbox, `host.microsandbox.internal` resolves to the host machine, same idea as Docker's `host.docker.internal`. Use it to call a dev server, database, or other process running on your machine without hard-coding an IP.

```bash
# Inside the sandbox:
curl http://host.microsandbox.internal:8080
```

The name is wired through `/etc/hosts` and the DNS interceptor, so both standard resolvers and tools that bypass `/etc/hosts` (like `dig`) find it.

The default policy denies host access — the sandbox gateway sits in a private range, same as any other local destination. Add the `host` group to open it:

<CodeGroup>
```rust Rust
NetworkPolicy::builder()
    .default_deny()
    .egress(|e| e.allow_public().allow_host())
    .build()?
```

```go Go
sb, err := m.CreateSandbox(ctx, "dev-agent",
    m.WithImage("python"),
    m.WithNetwork(&m.NetworkConfig{
        DefaultEgress: m.PolicyActionDeny,
        Rules: []m.PolicyRule{
            {Action: m.PolicyActionAllow, Direction: m.PolicyDirectionEgress, Destination: "public"},
            {Action: m.PolicyActionAllow, Direction: m.PolicyDirectionEgress, Destination: "host"},
        },
    }),
)
```

```bash CLI
msb create python --name dev-agent \
    --net-rule "allow@public,allow@host"
```
</CodeGroup>

### Loopback vs host: a common trap

`loopback` means the sandbox's own `127.0.0.1`. It does not mean your laptop's localhost.

Use `host` when the sandbox needs to reach a service running on your machine via `host.microsandbox.internal`. This is the Docker-style "talk to the host" path.

Use `local` when you intentionally want to allow guest loopback, link-local addresses, and the host together. Cloud metadata stays separate; allow `meta` explicitly only if you need it.

## Protocol support

For most sandboxed workloads, networking behaves the way you'd expect:

- Normal egress **TCP** and **UDP** traffic works, including common tools and libraries like `curl`, `wget`, package managers, HTTP clients, database drivers, and DNS lookups.
- **DNS** queries are intercepted on the host side. See [DNS](/networking/dns) for blocking, pinned resolvers, and query timeouts.
- **ICMP echo** is supported: pinging external hosts works on systems that support unprivileged ICMP echo sockets.
- **Ingress** on published ports is gated by the same policy. Denied connections drop with TCP RST so the peer sees `ECONNRESET`.

<Note>
**Raw sockets** and **full ICMP forwarding** are not supported because they require elevated privileges on the host. Tools that depend on richer ICMP behavior, such as `traceroute`, are outside the current scope.
</Note>

## Next

- [DNS](/networking/dns): domain blocking, pinned nameservers, query timeouts
- [TLS interception](/networking/tls): HTTPS inspection with an auto-generated CA
- [Security model](/networking/security-model): how the stack defends against SSRF, rebinding, and metadata exfiltration
