Skip to content

Guides

Network egress policy

Apply deny-first outbound network rules for agents, services, package installs, and browser automation.

Network access is a sandbox capability. Treat it like filesystem writes or secret grants: start closed, add the smallest destination set that makes the workload useful, and record why the grant exists.

This page focuses on outbound egress. For backend network layout and port forwarding mechanics, see Networking.

LevelUse whenExample
No egressGenerated code, code interpreters, offline analysis.(default — deny-all)
Package registriesBuild or install steps need language registries.--net
Local developmentA developer needs common dev services.--net
Explicit allowlistProduction-like agent or service path.--allow-host api.example.com:443
UnrestrictedLocal debugging only.Avoid for untrusted workloads.

For security-sensitive examples, use no egress or explicit allowlists. Broad presets are convenience tools, not production policy.

Start closed:

Terminal window
mvmctl machine run --flake . --name agent-sandbox

Allow only package registries for install-heavy workflows:

Terminal window
mvmctl machine run --flake . --name build-sandbox --net

Allow named destinations:

Terminal window
mvmctl machine run --flake . --name agent-sandbox \
\
--allow-host api.openai.com:443 \
--allow-host github.com:443

Keep inbound ports separate from outbound egress:

Terminal window
mvmctl machine run --flake . --name api-dev --port 8080:8080

Opening a host port does not mean the guest should have outbound internet access.

Static declarations should make network posture reviewable:

network=mvm.network(mode="none")

For a reviewed outbound grant:

network=mvm.network(
mode="bridge",
egress=mvm.egress([
mvm.host_port("api.openai.com", 443),
]),
dns=mvm.dns_system(),
)

Use Declaration cookbook for the full decorator shape.

Do not let a model choose arbitrary network destinations. The application should map a capability to a fixed grant:

{
"tool": "web_fetch",
"network": {
"mode": "bridge",
"allow": [
{
"host": "api.openai.com",
"port": 443
}
]
}
}

Validation rules:

  • reject wildcard hosts, CIDRs, and user-provided raw proxy URLs unless the policy explicitly supports them;
  • allow only expected ports for each tool capability;
  • keep DNS policy explicit when the workload depends on names;
  • treat outbound detect/replace and inbound plaintext reinjection as separate policy decisions;
  • store the grant decision with the audit/run identifier;
  • return policy denial distinctly from transport or guest command failure.

If a tool path is allowed to send secrets or user data to a remote service, prefer runtime-owned mediation over raw pass-through. On owned cleartext paths, the runtime can detect secrets and structured PII, replace them with opaque flow-scoped tokens, and restore the original bytes only when the exact token returns on the owned response path. Do not claim this as general content reconstruction: paraphrased or transformed remote output should stay redacted or tokenized.

Browser and desktop automation should be treated as high-risk network users. They may carry cookies, sessions, downloads, and rendered user data.

Use a dedicated profile:

  • no broad host mounts;
  • short TTL;
  • explicit egress allowlist;
  • isolated persistent workspace if state must survive;
  • no credential-bearing browser profile unless the task requires it;
  • cold state or snapshots retained only with a clear retention rule.

When a workload is allowed to call AI APIs, you can meter token usage and cap it with a budget. Enable metering in the [network.ai] section of mvm.toml:

[network]
allow_hosts = ["api.openai.com:443"]
[network.ai]
metering = true
[network.ai.budget]
max_total_tokens = 1_000_000
max_input_tokens = 500_000
max_output_tokens = 500_000

Equivalent SDK declarations:

network=mvm.network(
mode="bridge",
egress=mvm.egress([
mvm.host_port("api.openai.com", 443),
]),
ai=mvm.ai_policy(
metering=True,
budget=mvm.ai_budget(
max_total_tokens=1_000_000,
max_input_tokens=500_000,
max_output_tokens=500_000,
),
),
)

Behavior:

  • Metering only inspects traffic to known AI providers (api.openai.com, *.openai.azure.com, api.anthropic.com). Arbitrary destinations are not parsed.
  • Only provider-reported usage is counted. For OpenAI, set stream_options.include_usage=true so streamed responses include a trailing usage block; Anthropic includes usage automatically.
  • Budget enforcement is best-effort at response time: the request that crosses the budget is allowed and recorded; the next AI request is refused.
  • Metrics are published as mvm_instance_ai_requests_total, mvm_instance_ai_tokens_input_total, mvm_instance_ai_tokens_output_total, and mvm_instance_ai_tokens_total_total. Audit records contain counts and provider/model metadata only — never request or response bodies, headers, or credentials.

The enforcement mechanism is backend-specific:

  • Linux Firecracker paths can enforce bridge traffic with host firewall rules.
  • macOS backends enforce through their host-side network layer.
  • Some local debugging backends may have weaker isolation tiers.

Docs and examples should describe the product policy first, then name backend limits where the mechanism matters. Do not turn a backend convenience path into a product security claim without tests and claim-ledger evidence.

Before allowing egress:

  • name the exact host and port;
  • confirm why the workload needs it;
  • confirm whether DNS is needed;
  • confirm whether the destination can receive secrets or user data;
  • confirm logs and receipts will not expose payload data;
  • confirm cleanup or retention behavior for any downloaded files.