# Network Isolation and Egress Policy

How to configure network modes in DynoCap, enforce strict egress boundaries, and prevent data exfiltration.

HTML: https://dynocap.dev/docs/how-to/network-isolation-and-egress

A core security premise of DynoCap is that code generated or executed by an AI agent must not have unrestricted access to the Internet by default.

This guide explains how to configure network egress policies for both raw sandboxes and project tasks.

---

## Network Modes Overview

DynoCap supports three explicit network modes:

| Mode               | Virtual Network Device    | Outbound Permitted                                | Use Case                                                  |
| :----------------- | :------------------------ | :------------------------------------------------ | :-------------------------------------------------------- |
| `none` _(default)_ | None (no `virtio-net`)    | **Zero network traffic**                          | Running untrusted code, tests, and agent modifications.   |
| `filtered`         | `virtio-net` + Host proxy | **HTTPS (443) only** to npm registry and Git host | Dependency warming (`npm ci`) and public Git cloning.     |
| `nat`              | `virtio-net` + macOS NAT  | Unrestricted outbound                             | Tasks requiring external APIs or non-standard registries. |

---

## 1. Offline Mode (`--network none`)

By default, any `dynocap run` or `dynocap clone` command boots without a network device:

```sh
# Explicitly or by default
dynocap clone ./my-project sandbox --network none
```

Inside the Linux guest:

- The kernel exposes only the loopback interface (`lo`).
- There is no `eth0` or `enp0s1` virtual network adapter.
- Packet transmission is physically impossible at the hypervisor level.

Even if an attacker inside the VM executes `curl`, `nc`, or attempts DNS lookups, the operations fail immediately with `Network is unreachable`.

---

## 2. Filtered Egress Mode (`--network filtered`)

In filtered mode, DynoCap attaches a virtual network interface backed by a dedicated host-level user-space forwarder (`FilteredForwarder.swift`):

```sh
dynocap clone ./my-project sandbox --network filtered
```

### Allowlist Rules

The forwarder inspects initial TLS ClientHello packets and socket connections:

1. **Port 443 Only:** Non-HTTPS ports (e.g. HTTP on port 80, SSH on port 22, raw TCP) are dropped.
2. **Designated Hostnames:** exact names only, never their subdomains.
   - The task runtime's package registry, as declared in the prepared runtime manifest:
     - `node:24`, `node:22`: `registry.npmjs.org`
     - `python:3.14`: `pypi.org`, `files.pythonhosted.org`
     - `go:1.27`: `proxy.golang.org`, `sum.golang.org`
     - `shell:1`: none
   - The remote Git origin host if the project was cloned from an HTTPS URL.
   - A plain `dynocap run` sandbox has no runtime and keeps `registry.npmjs.org`.

A direct connection to another IP or domain (e.g., pastebins, command-and-control servers, webhooks) is terminated at the host boundary.

> **Filtered egress is not an exfiltration barrier.** The allowed hosts accept data: `npm publish` reaches `registry.npmjs.org`, `proxy.golang.org` fetches module paths on attacker-chosen domains, and `git push` reaches the repository host. Assume a filtered sandbox can send out anything it can read. See [What filtered egress does not stop](../explanation/security-and-isolation.mdx#what-filtered-egress-does-not-stop).

### Inspecting Active Network Configuration

You can verify the network policy of any running sandbox:

```sh
dynocap inspect sandbox --json
```

The output will clearly state:

```json
{
  "network": "filtered",
  "phase": "running"
}
```

---

## 3. Unrestricted NAT Mode (`--network nat`)

For tasks that legitimately require general Internet access, such as downloading datasets, interacting with external web APIs, or installing system-level packages via `apk`:

```sh
dynocap clone ./my-project sandbox --network nat
```

Under NAT mode:

- Apple's `VZNATNetworkDeviceAttachment` provides NAT routing through your Mac's active network interfaces.
- The guest obtains an IP via DHCP from macOS.
- Outbound traffic is unrestricted; inbound traffic from external networks remains unreachable.
- The guest can reach your local network and services your Mac listens on through any interface, such as development servers bound to `0.0.0.0`.

> **Caution:** Do not use `nat` for untrusted agent evaluations when secrets or sensitive files are in scope, as the agent can send arbitrary payloads over the Internet.

---

## Network Restrictions in TypeScript SDK & MCP

When automating tasks programmatically or via LLM tool-calling (MCP):

### TypeScript SDK

The SDK defaults to `none`:

```ts
import { Sandbox, Task } from "@dynocap/sdk";

// Offline task
const task = await Task.start({
  source: "./my-project",
  network: "none",
});

// Require offline enforcement (throws if sandbox has network)
const task = await Task.open("my-task", { requireOffline: true });
```

### Model Context Protocol (MCP) Server

To protect developers, the `@dynocap/mcp` server rejects `nat` and `filtered` commands by default. You must explicitly start the MCP server with environment variables enabling them:

```sh
# Enable filtered network mode for agents
DYNOCAP_MCP_ALLOW_FILTERED=1 npx @dynocap/mcp

# Enable unrestricted NAT mode
DYNOCAP_MCP_ALLOW_NAT=1 npx @dynocap/mcp
```

If an LLM requests a sandbox with `network: "nat"` and the flag is not set, the tool returns:

```text
NAT is disabled by this MCP host; start the server with DYNOCAP_MCP_ALLOW_NAT=1 to permit it
```
