---
title: Overview
description: Control network access and isolation
keywords: ["sandbox networking", "network policy", "port forwarding", "egress"]
icon: "network-wired"
---

Microsandbox checks sandbox traffic against a host-controlled network policy. Start with the defaults, then allow only the destinations and ports your workload needs.

## Defaults

Sandboxes can reach the public internet by default. Private networks, the host, loopback, link-local addresses, and cloud metadata endpoints are blocked. Inbound traffic needs a published port.

To isolate a sandbox from the network:

<CodeGroup>
```typescript TypeScript
import { Sandbox } from "microsandbox";

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

```rust Rust
use microsandbox::Sandbox;

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

```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()),
)
```

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

```bash CLI
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.

Ruby exposes network disablement, host/port allowlists, proxies, and basic TLS interception. Deployment profiles, policy profiles, NAT64, port publishing, and rate limits require TypeScript, Rust, Python, Go, or the CLI; see the [Ruby networking reference](/sdk/ruby/networking) for its available options.

## Deployment profiles

Local sandboxes use `single-tenant` by default. Use `multi-tenant` for shared hosts: it enforces public-network access, DNS rebinding protection, and stricter limits. Sandbox policies can restrict access further. Cloud chooses its own profile.

<CodeGroup>
```typescript TypeScript
import { Sandbox } from "microsandbox";

await using sb = await Sandbox.builder("shared-worker")
    .image("python")
    .deploymentProfile("multi-tenant")
    .create();
```

```rust Rust
use microsandbox::sandbox::DeploymentProfile;
use microsandbox::Sandbox;

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

```python Python
from microsandbox import DeploymentProfile, Sandbox

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

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

```bash CLI
msb create python --name shared-worker --deployment-profile multi-tenant
```
</CodeGroup>

<Accordion title="Deployment restrictions and operator settings">

In `multi-tenant` mode, traffic must pass both the platform's public-network policy and the sandbox's policy. Custom DNS servers and interface overrides are removed; host CA import and published ports are disabled. The sandbox cannot relax these restrictions.

An operator can set `deployment_profile` in `~/.microsandbox/config.json` or on `LocalBackend`. This overrides the sandbox's choice on create and restart. Managed cloud requests do not carry a deployment profile; the hosting driver selects it.

</Accordion>

<span id="connection-limits" />

<Accordion title="Connection limits and compatibility">

The per-sandbox `maxTcpConnections` setting optionally limits concurrent guest-initiated 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.

`maxInboundTcpConnections` limits active inbound TCP connections across published ports; `0` means unlimited. Omission uses the runtime default: unlimited in **0.7.9+**, or **256** in older versions. Explicit overrides require runtime support or fail with an upgrade-required error. This local-only limit is separate from the listener queue (`tcpAcceptQueueSize`).

When the active inbound limit is full, new connections wait in a shared FIFO queue across published TCP ports. Up to 1,024 connections can wait, for at most 30 seconds each. A full queue or expired wait resets the connection; existing connections continue. Waiting connections use host sockets and file descriptors, but guest-side TCP buffers are allocated only on admission. `tcpAcceptQueueSize` controls the separate kernel listen backlog for each port. With unlimited inbound connections, the listener pauses when its handoff channel fills; no active-slot waiting deadline applies.

On 0.7.9+, finished connections leave the active budget after response delivery. The runtime retains up to 256 finished sockets in TIME-WAIT, evicting the oldest under pressure and shortening protection against delayed packets. Host writes stalled for 30 seconds are closed; successful writes restart the timer.

Each TCP socket allocates 128 KiB of buffers. An outbound cap of 1,024 allows up to 128 MiB; an inbound cap of 256 plus 256 retained sockets allows up to 64 MiB. The retention pool can use 32 MiB even with a small active cap. These figures exclude relay channels, host socket buffers, and other allocations. Unlimited connections still face memory, file-descriptor, and source-port limits.

</Accordion>

## High-level profiles

Combine `public`, `private`, and `host` to allow those destination groups. Use `none` to deny all traffic or `all` to allow all traffic. This example allows public and private networks:

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

await using sb = await Sandbox.builder("worker")
    .image("python")
    .network((n) => n.policy(NetworkPolicy.fromProfiles(["public", "private"])))
    .create();
```

```rust Rust
use microsandbox::{NetworkPolicy, NetworkProfile, Sandbox};

let policy = NetworkPolicy::from_profiles([NetworkProfile::Public, NetworkProfile::Private]);
let sb = Sandbox::builder("worker")
    .image("python")
    .network(|n| n.policy(policy))
    .create()
    .await?;
```

```python Python
from microsandbox import Network, NetworkProfile, Sandbox

sb = await Sandbox.create(
    "worker",
    image="python",
    network=Network.from_profiles(NetworkProfile.PUBLIC, NetworkProfile.PRIVATE),
)
```

```go Go
sb, err := m.CreateSandbox(ctx, "worker",
    m.WithImage("python"),
    m.WithNetwork(m.NetworkPolicy.FromProfiles(m.NetworkProfilePublic, m.NetworkProfilePrivate)),
)
```

```bash CLI
msb create python --name worker --net "public,private"
```
</CodeGroup>

Add `host` for services on your machine; see [Reaching the host](#reaching-the-host). Duplicate profiles are ignored. Explicit rules placed before profile rules take precedence.

## Low-level custom policies

For finer control, set default actions and add rules. The first matching rule wins. This example allows public HTTPS and gateway DNS while denying other outbound traffic:

<CodeGroup>
```typescript TypeScript
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
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("restricted-worker")
    .image("alpine")
    .network(|n| n.policy(policy))
    .create()
    .await?;
```

```python Python
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
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
msb create alpine --name restricted-worker \
  --net-default-egress deny \
  --net-rule "allow@public:tcp:443,allow@dns"
```
</CodeGroup>

Rules can match destination groups, IPs, CIDRs, domains, and ports. See the [CLI rule syntax](/cli/sandbox-commands#network-rule-syntax) or an [SDK reference](#reference) for exact options.

## NAT64 prefixes

NAT64 lets IPv6 networks reach IPv4 destinations. Microsandbox checks both addresses against policy, so translated private and metadata addresses stay blocked. The standard `64:ff9b::/96` prefix is included by default.

If your local network routes another `/96` prefix, add it like this:

<CodeGroup>
```typescript TypeScript
import { Sandbox } from "microsandbox";

await using sb = await Sandbox.builder("translated")
    .image("python")
    .network((n) => n.nat64Prefix("64:ff9b:1::/96"))
    .create();
```

```rust Rust
use microsandbox::Sandbox;

let prefix = "64:ff9b:1::/96".parse()?;
let sb = Sandbox::builder("translated")
    .image("python")
    .network(|n| n.nat64_prefix(prefix))
    .create()
    .await?;
```

```python Python
from microsandbox import Network, Sandbox

sb = await Sandbox.create(
    "translated",
    image="python",
    network=Network(nat64_prefixes=("64:ff9b::/96", "64:ff9b:1::/96")),
)
```

```go Go
sb, err := m.CreateSandbox(ctx, "translated",
    m.WithImage("python"),
    m.WithNetwork(&m.NetworkConfig{
        NAT64Prefixes: []string{"64:ff9b::/96", "64:ff9b:1::/96"},
    }),
)
```

```bash CLI
msb create python --name translated --net-nat64-prefix 64:ff9b:1::/96
```
</CodeGroup>

This setting classifies traffic; it does not set up NAT64 routing. Cloud rejects custom or empty prefix lists. See [CLI options](/cli/sandbox-commands#network-profiles) for details.

## Hostname rules and HTTPS

Allow specific domains with hostname rules. By default, HTTPS allowed only by a hostname rule requires [TLS inspection](/networking/tls), so microsandbox can check the hostname inside the request:

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

const policy = NetworkPolicy.builder()
    .defaultDeny()
    .egress((e) => e.tcp().port(443).allowDomain("github.com"))
    .egress((e) => e.udp().tcp().port(53).allowHost())
    .build();

await using sb = await Sandbox.builder("github-only")
    .image("python")
    .network((n) => n.policy(policy))
    .interceptTls()
    .create();
```

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

let policy = NetworkPolicy::builder()
    .default_deny()
    .egress(|e| e.tcp().port(443).allow_domains(["github.com"]))
    .egress(|e| e.udp().tcp().port(53).allow_host())
    .build()?;

let sb = Sandbox::builder("github-only")
    .image("python")
    .network(|n| n.policy(policy))
    .intercept_tls()
    .create()
    .await?;
```

```python Python
from microsandbox import Action, Destination, Network, NetworkPolicy, Protocol, Rule, Sandbox

sb = await Sandbox.create(
    "github-only",
    image="python",
    network=Network(policy=NetworkPolicy(
        default_egress=Action.DENY,
        rules=(
            Rule.allow(destination=Destination.domain("github.com"), protocol=Protocol.TCP, port=443),
            *Rule.allow_dns(),
        ),
    )),
    intercept_tls=True,
)
```

```go Go
sb, err := m.CreateSandbox(ctx, "github-only",
    m.WithImage("python"),
    m.WithNetwork(&m.NetworkConfig{
        DefaultEgress: m.PolicyActionDeny,
        Rules: []m.PolicyRule{
            {Action: m.PolicyActionAllow, Direction: m.PolicyDirectionEgress, Destination: "github.com", Protocol: m.PolicyProtocolTCP, Port: "443"},
            m.Rule.AllowDNS(),
        },
    }),
    m.WithInterceptTLS(),
)
```

```ruby Ruby
sb = Microsandbox::Sandbox.create(
  "github-only",
  image: "python",
  network: { allowed_hosts: ["github.com"], allowed_ports: [443] },
  intercept_tls: true
)
```

```bash CLI
msb create python --name github-only \
  --net-default-egress deny \
  --net-rule "allow@github.com:tcp:443,allow@dns" \
  --tls-intercept
```
</CodeGroup>

<Accordion title="How strict hostname checks work">

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.

</Accordion>

## Port mapping

Publish a guest port to your host machine. This example forwards host port `8080` to guest port `80`, listening on `127.0.0.1` by default. Startup fails if a host port is already in use. Port publishing is local-only.

<CodeGroup>
```typescript TypeScript
import { Sandbox } from "microsandbox";

await using sb = await Sandbox.builder("api")
    .image("python")
    .port(8080, 80)
    .create();
```

```rust Rust
use microsandbox::Sandbox;

let sb = Sandbox::builder("api")
    .image("python")
    .port(8080, 80)
    .create()
    .await?;
```

```python Python
from microsandbox import PortBinding, Sandbox

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

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

```bash CLI
msb create python --name api -p 8080:80
```
</CodeGroup>

<Accordion title="Bind addresses, port ranges, and Windows">

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.

CLI `-p` and YAML `ports` lists also accept inclusive, equal-length port ranges: `-p 8000-8002:80-82` forwards three TCP ports, while `-p 0.0.0.0:10240-11264:10240-11264/udp` publishes 1,025 UDP ports. Ranges expand into the same individual mappings used by the SDKs, with one host socket per port. See [Published ports](/cli/sandbox-commands#published-ports) for validation and host resource requirements.

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.

</Accordion>

## Rate limits

Limit inbound or outbound bandwidth and packet rate on local sandboxes. Omitted limits leave traffic unthrottled; changes take effect on the next start. Cloud does not support this setting.

This example allows 1 MiB/s outbound, a one-time 512 KiB burst, and 1,000 packets per second:

<CodeGroup>
```typescript TypeScript
import { Sandbox } from "microsandbox";

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
use std::time::Duration;

use microsandbox::size::SizeExt;
use microsandbox::Sandbox;

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
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
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
msb create python --name throttled \
  --net-egress-bandwidth 1M/1s \
  --net-egress-bandwidth-burst 512K \
  --net-egress-ops 1000/1s
```
</CodeGroup>

<Accordion title="How limits and bursts are applied">

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.

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.

</Accordion>

<span id="what-a-denied-http-request-sees" />

## HTTP denial responses

New local sandboxes return JSON `403 Forbidden` responses for supported denied requests by default, with `code`, `message`, and `domain` fields. Set `deny_response: false` to close denied connections without a response. This requires a supporting runtime; cloud behavior is unchanged.

In JSON mode, `network_deny_message` and `secret_deny_message` customize the respective messages. Messages are used as-is; the destination is in `domain`. Changing a message or format does not override an explicit `deny_response: false`.

Select `deny_response_format: text` for legacy behavior: network denials return plain text, `deny_message` substitutes `{host}`, and secret violations close without a response. The old message setting remains supported but is deprecated for new integrations. Each mode uses only its own message settings. Omitted settings use the runtime defaults: responses enabled with JSON format. Explicit settings take precedence. Cloud accepts the default settings without applying them; enabled custom settings are local-only.

<CodeGroup>
```typescript TypeScript
await using sb = await Sandbox.builder("agent")
    .image("python")
    .network((n) => n
        .policy(policy)
        .tls((t) => t)
        .http((h) => h
            .denyResponse(true)
            .denyResponseFormat("json")
            .networkDenyMessage("Ask the user for network access.")
            .secretDenyMessage("Ask the user to review secret access.")))
    .create();
```

```rust Rust
use microsandbox::sandbox::HttpDenyResponseFormat;

let sb = Sandbox::builder("agent")
    .image("python")
    .network(|n| n
        .policy(policy)
        .tls(|t| t.enabled(true))
        .http(|h| h
            .deny_response(true)
            .deny_response_format(HttpDenyResponseFormat::Json)
            .network_deny_message("Ask the user for network access.")
            .secret_deny_message("Ask the user to review secret access.")))
    .create()
    .await?;
```

```python Python
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_response_format="json",
            network_deny_message="Ask the user for network access.",
            secret_deny_message="Ask the user to review secret access.",
        ),
    ),
)
```

```go Go
sb, err := m.CreateSandbox(ctx, "agent",
    m.WithImage("python"),
    m.WithNetwork(&m.NetworkConfig{
        Rules:           rules,
        TLS:             &m.TLSConfig{},
        HTTP: &m.HTTPConfig{
            DenyResponse: true,
            DenyResponseFormat: m.HTTPDenyResponseJSON,
            NetworkDenyMessage: "Ask the user for network access.",
            SecretDenyMessage: "Ask the user to review secret access.",
        },
    }),
)
```

```ruby Ruby
sb = Microsandbox::Sandbox.create(
  "agent",
  image: "python",
  network: { allowed_hosts: ["example.com"], allowed_ports: [80, 443] },
  http: {
    deny_response: true,
    deny_response_format: "json",
    network_deny_message: "Ask the user for network access.",
    secret_deny_message: "Ask the user to review secret access."
  }
)
```

```yaml sandbox.yml
network:
  policy: none
  allow: [github.com]
  http:
    deny_response: true
    deny_response_format: json
    network_deny_message: "Ask the user for network access."
    secret_deny_message: "Ask the user to review secret access."
```
</CodeGroup>

Check the response status, or use `curl --fail` to make a `403` return a nonzero exit code.

<Accordion title="Which requests receive a response?">

Responses work for HTTP/1.0 and HTTP/1.1. HTTPS requires [TLS interception](/networking/tls) on the destination port, with no bypass for the host. Network-policy denials never reach the upstream server.

In JSON mode, secret-policy denials can also return a response for the first request on a connection, before any upstream response bytes have reached the guest. The connection may already be open when a placeholder is detected; forwarding stops at the violation.

Secret-policy violations still close the connection for later or pipelined requests, after an upstream response (including `100 Continue`), and for `HEAD`, `CONNECT`, `block-and-terminate`, or unsupported body substitution.

DNS denies still return `NXDOMAIN`. HTTP/2, other protocols, malformed or incomplete requests, and silent clients close without an HTTP response.

</Accordion>

<Accordion title="Response format">

JSON responses use `Content-Type: application/json`. Match `code` to identify the error and display `message` to the user. `domain` is the requested hostname, an IP address fallback for network denials, or `null` when unknown.

Network-policy denial:

```json
{
  "code": "network_policy_denied",
  "message": "Request blocked by network policy.",
  "domain": "example.com"
}
```

Secret-policy denial:

```json
{
  "code": "secret_policy_denied",
  "message": "Request blocked by secret policy.",
  "domain": "example.com"
}
```

Both custom messages are JSON-escaped. An omitted message uses the default; an empty string leaves `message` empty (Go treats an empty string as omitted). Text mode works with older runtimes that support denial responses. JSON mode requires the new runtime capability; the SDK returns an upgrade-required error if it is missing.

</Accordion>

## Reaching the host

`host.microsandbox.internal` points to the host machine from inside a local sandbox. The default policy blocks it, so allow the `host` group when the guest needs a local service:

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

await using sb = await Sandbox.builder("devbox")
    .image("python")
    .network((n) => n.policy(NetworkPolicy.fromProfiles(["public", "host"])))
    .create();
```

```rust Rust
use microsandbox::{NetworkPolicy, NetworkProfile, Sandbox};

let policy = NetworkPolicy::from_profiles([NetworkProfile::Public, NetworkProfile::Host]);
let sb = Sandbox::builder("devbox")
    .image("python")
    .network(|n| n.policy(policy))
    .create()
    .await?;
```

```python Python
from microsandbox import Network, NetworkProfile, Sandbox

sb = await Sandbox.create(
    "devbox",
    image="python",
    network=Network.from_profiles(NetworkProfile.PUBLIC, NetworkProfile.HOST),
)
```

```go Go
sb, err := m.CreateSandbox(ctx, "devbox",
    m.WithImage("python"),
    m.WithNetwork(m.NetworkPolicy.FromProfiles(m.NetworkProfilePublic, m.NetworkProfileHost)),
)
```

```bash CLI
msb create python --name devbox --net "public,host"
```
</CodeGroup>

Guest `127.0.0.1` is inside the sandbox, not the host. On cloud, the `host` group refers to the cloud worker, not the machine running your SDK.

## Reference

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

## Next

- [DNS](/networking/dns): nameservers and domain blocking
- [Outbound proxies](/networking/outbound-proxy): route traffic through a proxy
- [TLS inspection](/networking/tls): inspect HTTPS traffic
- [Host sockets](/networking/host-sockets): connect to host IPC without a TCP port
- [Network security](/security/network): understand the policy boundary and defenses
