Network Isolation
By default, sandboxes have full access to the internet. If you need to run untrusted user code or LLM agents that should not exfiltrate data, you can opt the sandbox into Network Isolation to completely block all outbound network traffic (egress).
When network isolation is enabled:
- Outbound network calls (e.g., HTTP requests, database connections, DNS lookups) are blocked immediately by the host firewall.
- Ingress requests on exposed ports (like public URLs or TCP exposures) are still permitted.
Enable Network Isolation
Section titled “Enable Network Isolation”To isolate a sandbox, set networkBlockAll (or network_block_all) to true when creating the sandbox.
const sandbox = await client.create({ image: 'ubuntu:22.04', networkBlockAll: true,})sandbox = client.create({ 'image': 'ubuntu:22.04', 'networkBlockAll': True,})sandbox, err := client.Create(ctx, sdktypes.CreateSandboxOptions{ Image: "ubuntu:22.04", NetworkBlockAll: true,})let sandbox = client.create(CreateOptions { image: "ubuntu:22.04".to_string(), network_block_all: Some(true), ..Default::default()})?;Sandbox sandbox = client.create( new CreateOptions() .setImage("ubuntu:22.04") .setNetworkBlockAll(true));Verify Network Isolation
Section titled “Verify Network Isolation”Once isolated, executing a command inside the sandbox that requires outbound network access (like curl or package installers) will fail.
const result = await sandbox.exec({ command: 'curl -I https://google.com',})console.log(`Exit code: ${result.exitCode}`) // Non-zero exit code due to blocked egressresult = sandbox.exec({ 'command': 'curl -I https://google.com'})print(f"Exit code: {result['exitCode']}")result, err := sandbox.Exec(ctx, sdktypes.ExecRequest{ Command: "curl -I https://google.com",})if err != nil { return err}fmt.Printf("Exit code: %d\n", result.ExitCode)let result = sandbox.exec(ExecRequest { command: "curl -I https://google.com".to_string(), ..Default::default()})?;println!("Exit code: {}", result.exit_code);import ai.aerol.microvm.model.ExecRequest;import ai.aerol.microvm.model.ExecResult;
ExecResult result = sandbox.exec( new ExecRequest().setCommand("curl -I https://google.com"));System.out.println("Exit code: " + result.exitCode);Ingress with Network Isolation
Section titled “Ingress with Network Isolation”Network isolation only blocks outbound requests. It does not affect ingress traffic to ports exposed via the SDK's port exposure APIs:
// Expose port 80 to the public internetconst exposure = await sandbox.exposePort(80)console.log(`Access the isolated sandbox at: ${exposure.url}`)exposure = sandbox.expose_port(80)print(f"Access the isolated sandbox at: {exposure['url']}")exposure, err := sandbox.ExposePort(ctx, 80)if err != nil { return err}fmt.Printf("Access the isolated sandbox at: %s\n", exposure.PublicURL)use aerolvm_sdk::ExposeOptions;
let exposure = sandbox.expose_port(80, ExposeOptions::http())?;if let aerolvm_sdk::ExposeResult::Http { url } = exposure { println!("Access the isolated sandbox at: {}", url);}import ai.aerol.microvm.model.ExposeResult;
ExposeResult exposure = sandbox.exposePort(80);System.out.println("Access the isolated sandbox at: " + exposure.publicURL);Services running inside the sandbox remain reachable via their exposed public URLs, even though the sandbox itself cannot initiate connection requests to the internet.
Selective Egress (Allowlist / Blocklist)
Section titled “Selective Egress (Allowlist / Blocklist)”A blanket block is often too strict — an agent may need to reach one specific API while everything else stays blocked. Use networkAllowOut or networkDenyOut to define a per-CIDR egress policy, enforced by the same host firewall:
networkAllowOut— the sandbox may reach only these CIDRs; all other outbound traffic is dropped.networkDenyOut— the sandbox may reach anything except these CIDRs.
The two are mutually exclusive. A full block is still expressed with networkBlockAll rather than a 0.0.0.0/0 deny.
// Allow only 1.1.1.0/24 and 8.8.8.8; block everything else.const sandbox = await client.create({ image: 'ubuntu:22.04', networkAllowOut: ['1.1.1.0/24', '8.8.8.8/32'],})sandbox = client.create({ 'image': 'ubuntu:22.04', 'networkAllowOut': ['1.1.1.0/24', '8.8.8.8/32'],})sandbox, err := client.Create(ctx, sdktypes.CreateSandboxOptions{ Image: "ubuntu:22.04", NetworkAllowOut: []string{"1.1.1.0/24", "8.8.8.8/32"},})let sandbox = client.create(CreateOptions { image: "ubuntu:22.04".to_string(), network_allow_out: Some(vec!["1.1.1.0/24".to_string(), "8.8.8.8/32".to_string()]), ..Default::default()})?;import java.util.List;
Sandbox sandbox = client.create( new CreateOptions() .setImage("ubuntu:22.04") .setNetworkAllowOut(List.of("1.1.1.0/24", "8.8.8.8/32")));To invert the policy — block a few destinations and allow the rest — pass the same shape to networkDenyOut instead:
const sandbox = await client.create({ image: 'ubuntu:22.04', networkDenyOut: ['10.0.0.0/8'], // block the internal range, allow the rest})sandbox = client.create({ 'image': 'ubuntu:22.04', 'networkDenyOut': ['10.0.0.0/8'],})sandbox, err := client.Create(ctx, sdktypes.CreateSandboxOptions{ Image: "ubuntu:22.04", NetworkDenyOut: []string{"10.0.0.0/8"},})let sandbox = client.create(CreateOptions { image: "ubuntu:22.04".to_string(), network_deny_out: Some(vec!["10.0.0.0/8".to_string()]), ..Default::default()})?;import java.util.List;
Sandbox sandbox = client.create( new CreateOptions() .setImage("ubuntu:22.04") .setNetworkDenyOut(List.of("10.0.0.0/8")));The policy is persisted with the sandbox and reapplied automatically across stop/start cycles and host restarts, just like networkBlockAll.
Disabling Public Exposure
Section titled “Disabling Public Exposure”The options above control outbound traffic. To stop a sandbox from being reachable from the public internet at all, set allowPublicTraffic to false at create time. The sandbox can then never be exposed — exposePort returns an error — while the platform's own access paths (the toolbox exec/file proxy and the SSH gateway) keep working, since they do not route through the public ingress.
const sandbox = await client.create({ image: 'ubuntu:22.04', allowPublicTraffic: false,})// sandbox.exposePort(80) now rejects — no public URL is ever created.sandbox = client.create({ 'image': 'ubuntu:22.04', 'allowPublicTraffic': False,})publicOff := falsesandbox, err := client.Create(ctx, sdktypes.CreateSandboxOptions{ Image: "ubuntu:22.04", AllowPublicTraffic: &publicOff,})let sandbox = client.create(CreateOptions { image: "ubuntu:22.04".to_string(), allow_public_traffic: Some(false), ..Default::default()})?;Sandbox sandbox = client.create( new CreateOptions() .setImage("ubuntu:22.04") .setAllowPublicTraffic(false));Because AerolVM has no auth-gated public route, "no public traffic" is enforced by refusing exposure rather than by serving a private URL. Omit the field (or set it to true) to keep the default behaviour, where exposed ports are publicly reachable.
Masking the Request Host
Section titled “Masking the Request Host”When you expose a port, traffic reaches your app through the per-sandbox public hostname (e.g. 3000-<sandbox-id>.<your-domain>), and that value arrives as the HTTP Host header. Many dev servers and frameworks reject requests whose Host they don't recognize:
- Vite (
server.allowedHosts) — "Blocked request. This host is not allowed." - webpack-dev-server (
allowedHosts) - Django (
ALLOWED_HOSTS), Rails host authorization
Set maskRequestHost to rewrite the upstream Host header to a value your app accepts — typically localhost. The public URL is unchanged; only the header your service sees changes. Leave it unset to pass the host through untouched.
const sandbox = await client.create({ image: 'node:22', maskRequestHost: 'localhost',})sandbox = client.create({ 'image': 'node:22', 'maskRequestHost': 'localhost',})sandbox, err := client.Create(ctx, sdktypes.CreateSandboxOptions{ Image: "node:22", MaskRequestHost: "localhost",})let sandbox = client.create(CreateOptions { image: "node:22".to_string(), mask_request_host: Some("localhost".to_string()), ..Default::default()})?;Sandbox sandbox = client.create( new CreateOptions() .setImage("node:22") .setMaskRequestHost("localhost"));The rewrite applies to HTTP port exposures only and is enforced consistently whether the sandbox serves traffic directly or wakes from a paused state. Raw TCP and TLS-passthrough exposures carry no Host header, so the value is ignored for them.
Fine-Grained Quotas vs. Blanket Blocking
Section titled “Fine-Grained Quotas vs. Blanket Blocking”If you do not want to block all network traffic permanently, you can use the SDK's Network Usage & Quotas features to set limits on total bytes sent or received.
Using the limits API, you can:
- Define maximum ingress/egress bytes at create time.
- Retrieve live byte counters via
getNetworkUsage(). - Dynamically update limits at runtime using
setNetworkLimits().
For details on setting up quotas, see the Network Usage & Quotas guide.
Limitations
Section titled “Limitations”- IPv4 Only: Network isolation and byte limits are applied to IPv4 routes. If the sandbox has IPv6 routing enabled, those connections are not blocked.
- CIDR-based, not domain-based: Selective egress (
networkAllowOut/networkDenyOut) matches on IP CIDRs, not hostnames. There is no domain allowlist or DNS-only mode — allow the CIDRs your destination resolves to.