Capsa is experimental software. APIs may change without notice.
Skip to content

Network Security

Control what network resources sandboxes can access using network policies.

Default Behavior

By default, sandboxes have no network access:

rust,no_run
let vm = capsa::sandbox()
    .build()
    .await?;

// No network access by default

Enable Network with VirtualNetwork

Use VirtualNetwork to enable networking:

rust,no_run
let network = VirtualNetwork::new();

let vm = capsa::sandbox()
    .network(&network)?
    .build()
    .await?;

// Full network access via NAT

Network Policies

Fine-grained control over allowed destinations:

rust,no_run
let policy = NetworkPolicy::deny_all()
    .allow_domain("api.github.com")
    .allow_domain("api.anthropic.com");

let network = VirtualNetwork::with_gateway(
    Gateway::new("10.0.2.0/24").unwrap().policy(policy).unwrap()
);

let vm = capsa::sandbox()
    .network(&network)?
    .build()
    .await?;

Domain Filtering

Exact Domain

Allow a specific domain:

rust,no_run
let policy = NetworkPolicy::deny_all()
    .allow_domain("api.github.com");
// Allows: api.github.com
// Blocks: github.com, www.github.com, anything else

Wildcard Domain

Allow a domain and all subdomains:

rust,no_run
let policy = NetworkPolicy::deny_all()
    .allow_domain("*.github.com");
// Allows: api.github.com, raw.github.com, etc.
// Blocks: github.com (exact match not included)

Multiple Domains

Chain multiple domain rules:

rust,no_run
let policy = NetworkPolicy::deny_all()
    .allow_domain("api.anthropic.com")
    .allow_domain("*.github.com")
    .allow_domain("registry.npmjs.org")
    .allow_domain("pypi.org");

Common Use Cases

AI Agent Sandbox

Allow only the AI API:

rust,no_run
let policy = NetworkPolicy::deny_all()
    .allow_domain("api.anthropic.com")
    .allow_domain("api.openai.com");

let network = VirtualNetwork::with_gateway(
    Gateway::new("10.0.2.0/24").unwrap().policy(policy).unwrap()
);

let vm = capsa::sandbox()
    .network(&network)?
    .build()
    .await?;

Package Installation

Allow package registries:

rust,no_run
let policy = NetworkPolicy::deny_all()
    // Rust
    .allow_domain("crates.io")
    .allow_domain("static.crates.io")
    // Python
    .allow_domain("pypi.org")
    .allow_domain("files.pythonhosted.org")
    // Node
    .allow_domain("registry.npmjs.org");

let network = VirtualNetwork::with_gateway(
    Gateway::new("10.0.2.0/24").unwrap().policy(policy).unwrap()
);

let vm = capsa::sandbox()
    .network(&network)?
    .build()
    .await?;

CI/CD Build

Allow source control and build services:

rust,no_run
let policy = NetworkPolicy::deny_all()
    .allow_domain("*.github.com")
    .allow_domain("*.githubusercontent.com")
    .allow_domain("*.docker.io")
    .allow_domain("*.docker.com");

let network = VirtualNetwork::with_gateway(
    Gateway::new("10.0.2.0/24").unwrap().policy(policy).unwrap()
);

let vm = capsa::sandbox()
    .network(&network)?
    .build()
    .await?;

DNS Resolution

When using domain-based policies, DNS lookups are automatically allowed to resolve the permitted domains. The policy checks the resolved IP against the domain allow list.

Note: If any allow_domain(...) rules are present, DNS (UDP/53) is implicitly allowed ahead of other rules. This means explicit DNS deny/log rules will not apply to DNS queries in that case. If you need to block DNS, avoid domain rules and use IP-based rules instead.

Port Forwarding

Forward ports from host to sandbox:

rust,no_run
let network = VirtualNetwork::new();

let vm = capsa::sandbox()
    .interface(NetworkInterface::new(&network).forward_tcp(8080, 80))?  // host:8080 -> guest:80
    .build()
    .await?;

// Access sandbox's port 80 at localhost:8080

Combining Policies and Port Forwarding

rust,no_run
let policy = NetworkPolicy::deny_all()
    .allow_domain("api.example.com");

let network = VirtualNetwork::with_gateway(
    Gateway::new("10.0.2.0/24").unwrap()
        .policy(policy).unwrap()
);

let vm = capsa::sandbox()
    .interface(NetworkInterface::new(&network).forward_tcp(3000, 3000))?
    .build()
    .await?;

Complete Example

rust,no_run
use capsa::{VirtualNetwork, Gateway, NetworkPolicy, AccessMode};

async fn run_ai_agent() -> capsa::Result<()> {
    let policy = NetworkPolicy::deny_all()
        .allow_domain("api.anthropic.com");

    let network = VirtualNetwork::with_gateway(
        Gateway::new("10.0.2.0/24").unwrap().policy(policy).unwrap()
    );

    let vm = capsa::sandbox()
        .cpus(2)
        .memory_mb(1024)
        .network(&network)?
        .share("./workspace", "/workspace", AccessMode::ReadWrite)
        .build()
        .await?;

    let agent = vm.agent().await?;

    // AI-generated code can call the API but nothing else
    let result = agent.exec("python3").arg("/workspace/agent.py").run().await?;

    println!("Output: {}", result.stdout);

    vm.shutdown().await?;
    Ok(())
}

Next Steps

Released under the MIT License.