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

# Overview

> Control network access and isolation

All sandbox traffic flows through a host-controlled networking stack. From inside the VM it looks like a normal network interface; from the host side, every packet is checked against policy before it leaves.

## Defaults

By default, sandboxes can reach the public internet but cannot reach private networks, loopback, link-local addresses, or cloud metadata endpoints. Only published ports accept inbound traffic.

To block network access:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await using sb = await Sandbox.builder("isolated")
      .image("python")
      .disableNetwork()
      .create();
  ```

  ```rust Rust theme={null}
  let sb = Sandbox::builder("isolated")
      .image("python")
      .network(|n| n.enabled(false))
      .create()
      .await?;
  ```

  ```python Python theme={null}
  from microsandbox import Network, Sandbox

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

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

  ```ruby Ruby theme={null}
  sb = Microsandbox::Sandbox.create("isolated", image: "python", network: :none)
  ```

  ```bash CLI theme={null}
  msb create python --name isolated --no-net
  ```
</CodeGroup>

The TypeScript and Rust examples disable the network device. Python `Network.none()`, Go `NetworkPolicy.None()`, and CLI `--no-net` retain the device but deny traffic in both directions through policy.

## Deployment profiles

<Tooltip tip="Managed cloud deployments choose the platform isolation profile; per-sandbox and local-operator profile overrides are not accepted."><span className="msb-badge-local">Local-only <Icon icon="circle-info" size={11} /></span></Tooltip>

`single-tenant` is the default and preserves the network configuration requested by the sandbox. `multi-tenant` adds a host-runtime isolation floor: traffic must pass both the platform public-network policy and the sandbox's policy, and DNS rebinding protection is forced on. Custom DNS servers and interface overrides are removed, host CA import and published ports are disabled. Sandbox policy can further restrict this floor but cannot broaden it.

The per-sandbox `maxTcpConnections` setting optionally limits concurrent TCP connections. `maxConnections` remains a deprecated TCP-only alias. When omitted, `single-tenant` has no connection-count cap and `multi-tenant` defaults to 1,024. An explicit positive value overrides either default; zero explicitly selects unlimited. These are runtime defaults, not operator-enforced ceilings. Services accepting tenant configuration must enforce their own policy on overrides.

Legacy v0.6.x launch contracts keep their historical default of 256 TCP connections when the limit is omitted. The current SDK rejects explicit unlimited TCP connections, values above 4,096, or multi-tenant values above 256 when launching an older runtime because those peers cannot preserve the requested meaning. A current runtime also rejects legacy zero TCP limits: old zero meant deny all connections, not unlimited. Current-contract defaults and explicit limits are unchanged.

`maxUdpConnections` independently limits host-side UDP relay sessions. Omission is unlimited in `single-tenant` mode and defaults to 1,024 in `multi-tenant` mode; zero explicitly selects unlimited. At a finite limit, a new session evicts the least recently active session. Sessions expire after 60 seconds of inactivity. Historical runtime launch contracts do not support an explicit UDP limit, including zero; the SDK rejects that override with an upgrade-required error instead of silently ignoring it.

Each tracked TCP socket currently allocates 64 KiB for receiving and 64 KiB for
sending. A ceiling of 1024 therefore requires up to 128 MiB of socket-buffer
capacity per sandbox, in addition to other runtime allocations. Reserve that
capacity on the host when configuring a ceiling. Selecting unlimited does not
remove memory or operating-system resource constraints. Closing sockets remain tracked
until their TCP state permits removal. Host file descriptors and shared egress
source ports impose additional constraints.

For NAT64, microsandbox evaluates destinations in configured `/96` NAT64 prefixes against the embedded IPv4 address as well as the IPv6 address. The well-known `64:ff9b::/96` prefix is configured by default; add environment-specific routed prefixes only when your platform actually uses them.

Custom NAT64 prefixes are supported only for local sandboxes. Cloud creation rejects non-default or empty prefix lists because the cloud API cannot carry this setting.

<CodeGroup>
  ```typescript TypeScript theme={null}
  await using sb = await Sandbox.builder("shared-worker")
      .image("python")
      .deploymentProfile("multi-tenant")
      .create();
  ```

  ```rust Rust theme={null}
  use microsandbox::sandbox::DeploymentProfile;

  let sb = Sandbox::builder("shared-worker")
      .image("python")
      .deployment_profile(DeploymentProfile::MultiTenant)
      .create()
      .await?;
  ```

  ```python Python theme={null}
  from microsandbox import DeploymentProfile, Sandbox

  sb = await Sandbox.create(
      "shared-worker",
      image="python",
      deployment_profile=DeploymentProfile.MULTI_TENANT,
  )
  ```

  ```go Go theme={null}
  sb, err := m.CreateSandbox(ctx, "shared-worker",
      m.WithImage("python"),
      m.WithDeploymentProfile(m.DeploymentProfileMultiTenant),
  )
  ```

  ```bash CLI theme={null}
  msb create python --name shared-worker --deployment-profile multi-tenant
  ```
</CodeGroup>

An operator can set an authoritative profile with the top-level `deployment_profile` field in `~/.microsandbox/config.json` or programmatically on `LocalBackend`. That operator choice overrides the sandbox request on create and restart. Managed cloud create requests intentionally do not carry a deployment profile. The hosting driver selects it.

## High-level profiles

For common access shapes, compose high-level profiles. Every non-empty profile set automatically adds narrow DNS access through the sandbox gateway.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const policy = NetworkPolicy.fromProfiles(["public", "private"]);
  ```

  ```rust Rust theme={null}
  let policy = NetworkPolicy::from_profiles([
      NetworkProfile::Public,
      NetworkProfile::Private,
  ]);
  ```

  ```python Python theme={null}
  network = Network.from_profiles(NetworkProfile.PUBLIC, NetworkProfile.PRIVATE)
  ```

  ```go Go theme={null}
  network := m.NetworkPolicy.FromProfiles(
      m.NetworkProfilePublic,
      m.NetworkProfilePrivate,
  )
  ```

  ```bash CLI theme={null}
  msb run alpine --net "public,private"
  ```
</CodeGroup>

The composable profiles are `public`, `private`, and `host`. `none` and `all` remain terminal whole-policy choices rather than profiles. Duplicate profiles are ignored, generated rules use a stable order, and explicit low-level rules can be placed before generated profile rules to override them.

## Low-level custom policies

A policy has two defaults and an ordered list of rules. The first matching rule wins.

```text theme={null}
default_egress  : allow | deny
default_ingress : allow | deny
rules           : first match wins
```

For example, this creates a deny-by-default sandbox that can make HTTPS requests to the public internet and DNS requests through the host gateway:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { NetworkPolicy, Sandbox } from "microsandbox";

  await using sb = await Sandbox.builder("restricted-worker")
      .image("alpine")
      .network((n) => n.policy(
          NetworkPolicy.builder()
              .defaultDeny()
              .egress((e) => e.tcp().port(443).allowPublic())
              .egress((e) => e.udp().tcp().port(53).allowHost())
              .build(),
      ))
      .create();
  ```

  ```rust Rust theme={null}
  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("restricted-worker")
      .image("alpine")
      .network(|n| n.policy(policy))
      .create()
      .await?;
  ```

  ```python Python theme={null}
  from microsandbox import Action, DestGroup, Destination, Network, NetworkPolicy, Protocol, Rule, Sandbox

  sb = await Sandbox.create(
      "restricted-worker",
      image="alpine",
      network=Network(policy=NetworkPolicy(
          default_egress=Action.DENY,
          rules=(
              Rule.allow(destination=Destination.group(DestGroup.PUBLIC), protocol=Protocol.TCP, port=443),
              *Rule.allow_dns(),
          ),
      )),
  )
  ```

  ```go Go theme={null}
  sb, err := m.CreateSandbox(ctx, "restricted-worker",
      m.WithImage("alpine"),
      m.WithNetwork(&m.NetworkConfig{
          DefaultEgress: m.PolicyActionDeny,
          Rules: []m.PolicyRule{
              {Action: m.PolicyActionAllow, Direction: m.PolicyDirectionEgress, Destination: "public", Protocol: m.PolicyProtocolTCP, Port: "443"},
              m.Rule.AllowDNS(),
          },
      }),
  )
  ```

  ```bash CLI theme={null}
  msb create alpine --name restricted-worker \
    --net-default-egress deny \
    --net-rule "allow@public:tcp:443,allow@dns"
  ```
</CodeGroup>

Rules can target groups like `public`, `private`, and `host`, or specific IPs, CIDRs, domains, and port ranges. See the [CLI reference](/cli/sandbox-commands#network-rule-syntax) or your language's SDK networking reference for exact syntax.

## Hostname rules and HTTPS

Domain and domain-suffix rules start with the hostname microsandbox can observe. DNS rules see the query name, and TLS connections usually expose SNI before the encrypted session starts. That is enough to decide whether a connection may be opened, but it is not the same as seeing the HTTP request authority.

For plaintext HTTP, microsandbox can inspect the `Host` header and require it to match the hostname that policy allowed. For HTTPS without TLS interception, or for a host that is configured to bypass interception, the HTTP `Host` header and HTTP/2 `:authority` value are encrypted. In that case microsandbox cannot prove which application hostname the request is using after the TLS session starts.

Strict hostname policy mode is enabled by default: hostname allowlists fail closed unless microsandbox can inspect the request authority. In strict mode, hostname-based allow rules still work for plaintext HTTP and TLS-intercepted HTTPS. Non-intercepted HTTPS is denied when it would otherwise be allowed only by a hostname rule. IP, CIDR, group, and default allow rules are not treated as hostname-based allows. Set `--net-strict=false` or `network.strict: false` in sandbox configuration to opt out.

## Port mapping

<Tooltip tip="Publishing host ports is not available on microsandbox cloud; there is no local host to publish to."><span className="msb-badge-local">Local-only <Icon icon="circle-info" size={11} /></span></Tooltip>

Publish a guest port when a service inside the sandbox should be reachable from the host. Published ports bind to `127.0.0.1` by default.

<CodeGroup>
  ```typescript TypeScript theme={null}
  await using sb = await Sandbox.builder("api")
      .image("python")
      .port(8080, 80)
      .create();
  ```

  ```rust Rust theme={null}
  let sb = Sandbox::builder("api")
      .image("python")
      .port(8080, 80)
      .create()
      .await?;
  ```

  ```python Python theme={null}
  from microsandbox import PortBinding, Sandbox

  sb = await Sandbox.create("api", image="python", ports=[PortBinding.tcp(8080, 80)])
  ```

  ```go Go theme={null}
  sb, err := m.CreateSandbox(ctx, "api",
      m.WithImage("python"),
      m.WithPorts(map[uint16]uint16{8080: 80}),
  )
  ```

  ```bash CLI theme={null}
  msb create python --name api -p 8080:80
  ```
</CodeGroup>

Use an explicit bind address, such as `0.0.0.0`, only when you intentionally want to listen beyond localhost. `-p 8080:80` and SDK helpers like `.port(8080, 80)` bind to `127.0.0.1`; `-p 127.0.0.1:8080:80` is the same loopback-only shape. `-p 0.0.0.0:8080:80` or a specific LAN interface address makes the host listener reachable outside the machine, subject to your OS firewall and network policy.

If the host starts without IPv4 or IPv6 routes, microsandbox assigns private guest addresses for published ports unless guest addresses are already configured. A specific host bind address must still exist at startup.

On Windows, the first published port may trigger a Windows Defender Firewall prompt for `msb.exe` because the runtime opens a host listening socket. For local development, keep the bind address on `127.0.0.1`. Only allow private/public network access in the firewall prompt when you intentionally bind a published port beyond loopback.

## Rate limits

<Tooltip tip="Network traffic rate limits are available for local sandboxes. microsandbox cloud currently rejects this setting."><span className="msb-badge-local">Local-only <Icon icon="circle-info" size={11} /></span></Tooltip>

Network rate limits apply only to traffic crossing a sandbox's virtual network device. They do not limit CPU, memory, disk I/O, API requests, or how many sandboxes can run. If you do not configure a limiter, microsandbox does not throttle the sandbox's network bandwidth or packet rate. Once configured, the limiter is enforced independently for outbound (egress) and inbound (ingress) traffic.

For each direction, you can configure a bandwidth bucket measured in bytes per refill interval, a packet-rate bucket measured in packets per interval, or both. An omitted direction or bucket remains unthrottled. Buckets start full, refill continuously, and can include a one-time startup burst that does not refill. Limits are set at creation and take effect on the next sandbox start.

<CodeGroup>
  ```typescript TypeScript theme={null}
  await using sb = await Sandbox.builder("throttled")
      .image("python")
      .network((n) => n.rateLimiter((r) => r.egress((r) => r
          .bandwidth(1_048_576, 1_000)
          .bandwidthBurst(524_288)
          .ops(1_000, 1_000))))
      .create();
  ```

  ```rust Rust theme={null}
  use std::time::Duration;
  use microsandbox::size::SizeExt;

  let sb = Sandbox::builder("throttled")
      .image("python")
      .network(|n| n.rate_limiter(|r| r.egress(|r| r
          .bandwidth(1.mib(), Duration::from_secs(1))
          .bandwidth_burst(512.kib())
          .ops(1_000, Duration::from_secs(1)))))
      .create()
      .await?;
  ```

  ```python Python theme={null}
  from microsandbox import Network, NetworkRateLimiter, RateLimiter, Sandbox, TokenBucket

  sb = await Sandbox.create(
      "throttled",
      image="python",
      network=Network(
          rate_limiter=NetworkRateLimiter(
              egress=RateLimiter(
                  bandwidth=TokenBucket(size=1_048_576, refill_time_ms=1_000, one_time_burst=524_288),
                  ops=TokenBucket(size=1_000, refill_time_ms=1_000),
              ),
          ),
      ),
  )
  ```

  ```go Go theme={null}
  sb, err := m.CreateSandbox(ctx, "throttled",
      m.WithImage("python"),
      m.WithNetwork(&m.NetworkConfig{
          RateLimiter: &m.NetworkRateLimiterConfig{
              Egress: &m.RateLimiterConfig{
                  Bandwidth: &m.TokenBucketConfig{Size: 1 << 20, RefillTime: time.Second, OneTimeBurst: 512 << 10},
                  Ops:       &m.TokenBucketConfig{Size: 1000, RefillTime: time.Second},
              },
          },
      }),
  )
  ```

  ```bash CLI theme={null}
  msb create python --name throttled \
    --net-egress-bandwidth 1M/1s \
    --net-egress-bandwidth-burst 512K \
    --net-egress-ops 1000/1s
  ```
</CodeGroup>

The `--net-ingress-*` flags mirror the egress flags for inbound traffic (`--net-ingress-bandwidth`, `--net-ingress-bandwidth-burst`, `--net-ingress-ops`, `--net-ingress-ops-burst`). Sizes accept raw bytes plus `K`, `M`, and `G` suffixes; the interval defaults to one second when omitted. A frame larger than the bandwidth bucket is delivered once and the limiter then pauses long enough to pay it off, so oversized packets are throttled instead of stuck.

## What a denied HTTP request sees

Denied connections fail without an HTTP response by default. Set `http.deny_response` to `true` (`denyResponse(true)` in TypeScript) to return a readable `403 Forbidden` instead. This applies to HTTP/1.0 and HTTP/1.1 in plaintext or over HTTPS on an intercepted port (see [TLS interception](/networking/tls)). The gateway never dials upstream.

When enabled without a custom message, the response body is:

```text theme={null}
This host is not allowed by the sandbox network policy config.

Note to agent: `example.com` is not in the allowed-host list. Ask the user to add it to the sandbox network allow list.
```

Enabling denial responses requires a supporting local runtime. The SDK rejects `deny_response: true` on older runtimes and on cloud. With it disabled, existing workflows need no new capability.

An HTTP `403` is a response, not a connection error. Check the status in your client, or use `curl --fail` when relying on its exit code.

Setting a message alone does not enable responses. Configure HTTP’s `deny_message` (`denyMessage` in TypeScript) to replace the body; `{host}` is substituted with the blocked hostname. Use it to name the tool or workflow your agent should use to request access.

<CodeGroup>
  ```typescript TypeScript theme={null}
  await using sb = await Sandbox.builder("agent")
      .image("python")
      .network((n) => n
          .policy(policy)
          .tls((t) => t)
          .http((h) => h.denyResponse(true).denyMessage("{host} is blocked. Call the `RequestHostAccess` tool to ask the user for it.")))
      .create();
  ```

  ```rust Rust theme={null}
  let sb = Sandbox::builder("agent")
      .image("python")
      .network(|n| n
          .policy(policy)
          .tls(|t| t.enabled(true))
          .http(|h| h.deny_response(true).deny_message("{host} is blocked. Call the `RequestHostAccess` tool to ask the user for it.")))
      .create()
      .await?;
  ```

  ```python Python theme={null}
  from microsandbox import HttpConfig, Network, Sandbox, TlsConfig

  sb = await Sandbox.create(
      "agent",
      image="python",
      network=Network(
          policy=policy,
          tls=TlsConfig(),
          http=HttpConfig(deny_response=True, deny_message="{host} is blocked. Call the `RequestHostAccess` tool to ask the user for it."),
      ),
  )
  ```

  ```go Go theme={null}
  sb, err := m.CreateSandbox(ctx, "agent",
      m.WithImage("python"),
      m.WithNetwork(&m.NetworkConfig{
          Rules:           rules,
          TLS:             &m.TLSConfig{},
          HTTP: &m.HTTPConfig{DenyResponse: true, DenyMessage: "{host} is blocked. Call the `RequestHostAccess` tool to ask the user for it."},
      }),
  )
  ```

  ```ruby Ruby theme={null}
  sb = Microsandbox::Sandbox.create(
    "agent",
    image: "python",
    network: { allowed_hosts: ["example.com"], allowed_ports: [80, 443] },
    http: { deny_response: true, deny_message: "{host} is blocked. Ask the user to allow it." }
  )
  ```

  ```yaml sandbox.yml theme={null}
  network:
    policy: none
    allow: [github.com]
    http:
      deny_response: true
      deny_message: "{host} is blocked. Call the `RequestHostAccess` tool to ask the user for it."
  ```
</CodeGroup>

DNS-level denies (a name matched by a `deny` domain rule, or a deny-by-default policy without a DNS allow) still fail at resolution with `NXDOMAIN`, before any HTTP exchange. HTTPS on a port that is not intercepted, or to a name on the TLS bypass list, is closed without a response: the gateway cannot answer inside a TLS session it does not terminate. HTTP/2 and non-HTTP protocols are closed without an HTTP response. The gateway buffers fragmented request lines within a fixed size and time limit; incomplete requests, silent clients, and malformed input also close without a response.

## Reaching the host

<Tooltip tip="The host group is backend-relative; on microsandbox cloud it does not refer to the machine running the SDK or CLI, so expose local services through a reachable network endpoint."><span className="msb-badge-limited">Limited on cloud <Icon icon="circle-info" size={11} /></span></Tooltip>

From inside the sandbox, `host.microsandbox.internal` resolves to the host machine. The default policy denies host access, so allow the `host` group when a sandbox needs to call a dev server, database, or other local service.

```bash theme={null}
msb create python --name devbox --net "public,host"
```

`loopback` means the sandbox's own `127.0.0.1`, not your laptop's localhost. Use `host` for `host.microsandbox.internal`.

## Reference

For exact network APIs, see [TypeScript](/sdk/typescript/networking), [Rust](/sdk/rust/networking), [Python](/sdk/python/networking), or [Go](/sdk/go/networking). For CLI flags and configuration fields, see [Sandbox commands](/cli/sandbox-commands#network-profiles) and [Sandbox configuration](/cli/configuration#network).

## Next

* [DNS](/networking/dns): domain blocking, pinned nameservers, and query timeouts
* [Proxies](/networking/outbound-proxy): route outbound TCP and non-DNS UDP through an external SOCKS proxy
* [TLS inspection](/networking/tls): inspect HTTPS with an auto-generated CA
* [Host sockets](/networking/host-sockets): connect guest applications to local host IPC without opening a TCP port
* [Security model](/security/overview): understand the trust boundary, and [network defenses](/security/network) for SSRF, rebinding, and metadata protection


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.