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.