Network Isolation and Egress Policy

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:

Shell
# 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):

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

Inspecting Active Network Configuration

You can verify the network policy of any running sandbox:

Shell
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:

Shell
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:

TypeScript
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:

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

This page is also plain Markdown, for agents and for you.