How Session-Persistent Skill Sandboxes Enforce Per-Tenant Network Policy in WeKnora

WeKnora enforces per-tenant network policies by storing tenant-specific firewall rules in the sandbox configuration, binding them to persistent sessions via sandbox_config_id, and injecting these restrictions into the container runtime during launch.

Tencent/WeKnora isolates skill execution using session-persistent sandboxes that maintain consistent network restrictions across entire user conversations. Unlike ephemeral containers that terminate after each skill call, this architecture ensures that every interaction within a session adheres to the tenant's defined security boundaries while reusing the same isolated environment.

What Is a Session-Persistent Skill Sandbox?

A session-persistent sandbox is a long-running container instance that remains active for the duration of a user session. According to the source code in internal/types/tenant.go, each session record stores a sandbox_config_id that points to a specific tenant sandbox configuration. This foreign key binding ensures that when a session initializes, WeKnora either attaches to an existing sandbox instance or creates a new one that will be reused for all subsequent skill executions in that conversation.

This persistence model eliminates the overhead of container cold starts during multi-turn interactions while maintaining strict isolation guarantees between different tenants.

How Per-Tenant Network Policy Is Defined

Tenant administrators define network restrictions through the SandboxNetworkPolicy struct defined in internal/types/sandbox_network_policy.go. This configuration specifies:

  • Allowed DNS servers: An array of IP addresses the sandbox can query for domain resolution
  • Egress IP ranges: CIDR blocks defining permitted outbound destinations
  • Firewall rules: Implicit deny-all policies for any traffic outside the whitelist

The policy is stored as a JSON object within the tenant's sandbox configuration payload. When creating a sandbox config via the API (handled in internal/handler/sandbox_config.go), the network_policy field accepts this structure:

type SandboxNetworkPolicy struct {
    DNS      []string `json:"dns"`       // allowed DNS servers
    EgressIP []string `json:"egress_ip"` // allowed outbound IP CIDRs
}

Example configuration payload:

{
    "name": "Tenant-A Sandbox",
    "description": "Sandbox with strict egress",
    "config": {
        "sandbox_type": "docker",
        "docker": { "image": "wechatopenai/weknora-sandbox" },
        "network_policy": {
            "dns": ["10.0.0.53"],
            "egress_ip": ["10.0.0.0/24"]
        }
    }
}

Policy Persistence and Database Schema

The network policy persists in the tenant_sandbox_configs table, which stores the complete configuration including the network_policy JSON object in the config column. The database migration migrations/versioned/000083_session_sandbox_config.up.sql establishes this schema, ensuring that tenant-specific sandbox settings survive application restarts and can be referenced across multiple sessions.

This persistence layer decouples policy definition from runtime enforcement, allowing administrators to manage network rules independently of active sandbox instances.

Session-to-Sandbox Binding

When WeKnora creates a new session, it assigns a sandbox_config_id that links the conversation to a specific tenant configuration. As implemented in internal/types/tenant.go, this binding makes the sandbox session-persistent—the same container instance services every skill call within that session.

The sandbox manager maintains this association throughout the session lifecycle, ensuring that:

  1. All skill executions occur within the same network namespace
  2. The tenant's egress whitelist applies consistently across the conversation
  3. No cross-tenant traffic leakage occurs between different sandbox instances

Runtime Enforcement Mechanism

The actual network isolation happens during container initialization in the remote client implementations. The internal/sandbox/remote_client.go interface and its Docker-specific implementation docker_remote_client.go read the NetworkPolicy from the SandboxConfig and translate it into runtime-specific constraints.

Docker Remote Client Implementation

The DockerRemoteClient.launchSandbox() method constructs container arguments that enforce the tenant's network restrictions:

func (c *DockerRemoteClient) launchSandbox(cfg *SandboxConfig) error {
    // cfg.NetworkPolicy is of type SandboxNetworkPolicy
    args := []string{
        "--network", "none",               // start with no network
        "--dns", strings.Join(cfg.NetworkPolicy.DNS, ","),
        // later we attach a custom iptables rule for egress IPs
    }
    // run `docker run` with the above args …
}

This implementation launches containers with the --network none flag initially, then applies custom iptables rules that restrict outbound traffic to the CIDR ranges specified in cfg.NetworkPolicy.EgressIP.

Network Isolation at Runtime

When the container starts, the underlying runtime (Docker, Cube, or E2B) creates a custom network namespace that:

  • Permits DNS queries only to the tenant-specified servers
  • Blocks all outbound traffic except to whitelisted IP ranges
  • Prevents lateral movement between sandboxes belonging to different tenants

The enforcement happens at the infrastructure level, making it impossible for skills to bypass these restrictions through in-container configuration changes.

Dynamic Policy Updates

Network policies are not static. When administrators update a tenant's sandbox config via PUT /sandbox-configs/:id, the new policy is written immediately to the database. As documented in website-docs/04-api/02-api-sandbox-skills.md, these changes take effect through a rolling restart mechanism:

  1. The updated policy is stored in tenant_sandbox_configs
  2. Existing sandboxes continue with their current restrictions until termination
  3. New session sandboxes (or restarted existing ones) receive the updated rules
  4. All subsequent skill executions in affected sessions respect the latest egress whitelist

This approach balances immediate policy propagation with session stability, ensuring zero-downtime security updates.

Summary

Frequently Asked Questions

How does WeKnora store per-tenant network policies?

WeKnora stores network policies as JSON objects within the config column of the tenant_sandbox_configs table, defined by the migration migrations/versioned/000083_session_sandbox_config.up.sql. The SandboxNetworkPolicy struct in internal/types/sandbox_network_policy.go defines the schema, which includes arrays for allowed DNS servers and egress IP CIDR ranges. This approach keeps policy data versioned and tenant-isolated in the relational database.

What happens when a network policy is updated while sessions are active?

When administrators update a policy via the PUT /sandbox-configs/:id endpoint, the changes persist immediately to the database but do not terminate running sandboxes. Existing sessions continue with their current network restrictions until their sandbox instance restarts, while new sessions (or restarted existing ones) receive the updated policy. This rolling approach ensures continuous availability while maintaining eventual consistency with the latest security rules.

Which container runtimes support these network policies?

The internal/sandbox/remote_client.go interface abstracts the policy enforcement, allowing implementations for Docker, Cube, and E2B runtimes. The reference implementation in docker_remote_client.go demonstrates enforcement through Docker's --network flags and custom iptables rules. Each runtime adapter translates the SandboxNetworkPolicy into platform-specific network isolation mechanisms while maintaining the same tenant-level restrictions.

How does session persistence maintain network isolation across skill calls?

The sandbox_config_id foreign key in the sessions table ensures that a single container instance services all skill calls within a conversation. Because the sandbox is created once per session (rather than per skill invocation), the network namespace initialized with the tenant's egress whitelist remains constant throughout the session lifecycle. This persistence model guarantees that every interaction adheres to the same network boundaries without the overhead of repeated container creation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →