​
Network policy

A sandbox on the default host compute class allows outbound network access by default. To restrict access, pass a NetworkPolicy as the sandbox’s network_policy (singular). The policy lists the destinations the sandbox can reach:

from chalkcompute import Image, NetworkPolicy, Sandbox

policy = NetworkPolicy(
    allowed_hosts=[
        "api.openai.com",
        "github.com",
        "pypi.org",
    ],
)

sandbox = Sandbox(
    image=Image.debian_slim(),
    network_policy=policy,
).run()

Each entry in allowed_hosts is an exact hostname, a wildcard subdomain pattern, or *:

  • An exact hostname, such as github.com, matches only that host.
  • *.github.com matches every subdomain of github.com, such as api.github.com, but not github.com itself. To allow both, list both.
  • * matches every hostname.

Matching ignores case. Requests to any other destination are blocked. A policy with an empty allowed_hosts allows no hostnames.

NetworkPolicy accepts one policy per sandbox — network_policy takes a single NetworkPolicy, not a list.

Network policies apply only to the host compute class. A sandbox on k8s has the network access of the cluster it runs on.

​
CIDR-level routes

Use allowed_routes and denied_routes for destinations that don’t have a stable hostname — a VPC-peered range or an on-premise network, for example. denied_routes takes precedence over both allowed_routes and allowed_hosts:

from chalkcompute import NetworkPolicy

policy = NetworkPolicy(
    allowed_routes=[
        NetworkPolicy.Route("10.0.0.0/8", ports=[(5432, 5432)]),
    ],
    denied_routes=["169.254.169.254/32"],  # block the cloud metadata endpoint
)

Route(route, *, ports=...) takes a destination CIDR and an optional list of (start_port, end_port) ranges; an empty ports allows every port on that route.

​
Per-host request rules

For per-host request matching or rewriting — for example, substituting a Secret into an auth header before forwarding to an internal service — pass allowed_hosts as a mapping of hostname to a list of NetworkPolicy.Rule instead of a plain list:

from chalkcompute import NetworkPolicy

policy = NetworkPolicy(
    allowed_hosts={
        "internal-api.example.com": [
            NetworkPolicy.Rule(
                match=NetworkPolicy.Match(
                    path=NetworkPolicy.Matcher(starts_with="/v1/"),
                ),
                transforms=[
                    NetworkPolicy.Transformer(
                        headers={
                            "Authorization": "Bearer __chalk_secret_API_TOKEN",
                        },
                        headers_secrets={
                            "__chalk_secret_API_TOKEN": "INTERNAL_API_TOKEN",
                        },
                    ),
                ],
                forward_url="https://internal-api.example.com",
            ),
        ],
    },
)

Each Rule can match on request path (exact, starts_with, or regex), HTTP method, query-string entries, and headers — a rule’s transforms and forward_url only apply to requests matching every dimension set on match. The route matches the original request before any transforms run.

Transformer.headers_secrets maps each literal placeholder token to the name of a Chalk Secret. For a matching rule, Chalk first applies the literal values in Transformer.headers, then replaces each placeholder occurrence in ordinary request-header values with that Secret’s contents. Placeholders can also appear in headers supplied by the workload. Chalk does not substitute within pseudo-headers, authority or framing headers, hop-by-hop headers, or Chalk-owned routing headers.

Placeholder matching is case-sensitive. When occurrences overlap, Chalk selects the leftmost match and then the longest placeholder that starts at that position. Replacement is non-recursive, so inserted Secret contents are not scanned for additional placeholders. An absent placeholder has no effect, and an empty Secret value removes the matched placeholder text. Empty placeholder tokens are rejected. The sandbox never receives the Secret contents.


​
Managed SSH

Managed SSH lets a sandbox authenticate to approved SSH destinations using a Chalk Secret, without exposing the private key to the sandbox itself. It requires the host compute class. First, store the private key:

chalk secret set GITHUB_DEPLOY_KEY < /path/to/privkey/id_ed25519

Then configure the destination when creating the sandbox:

from chalkcompute import Image, SandboxClient, SshDest

sandbox_client = SandboxClient.from_env()
sandbox = sandbox_client.create(
    image=Image.debian_slim(),
    managed_ssh={
        "github": SshDest(
            "github.com",
            "GITHUB_DEPLOY_KEY",
            username="git",
        )
    },
)

private_key_secret is the name of a Chalk Secret whose value is an SSH private key. The map key creates the SSH alias used to connect, so the example supports ssh github and git clone github:example/repo.git. The default port is 22, but can be overridden by setting an explicit port.

By default, SSH accepts the server’s host key on the first connection and remembers it for the rest of the sandbox’s lifetime. Later connections fail if the server presents a different key. Set host_keys to override this, and to require specific host keys from the first connection. For example:

managed_ssh={
    "a-server": SshDest(
        "cloud.example.com",
        "KEY_FOR_SANDBOX_USE",
        username="my_user",
        host_keys=[
            "ssh-ed25519 AAAA...",
            "ecdsa-sha2-nistp256 AAAA...",
        ],
        port=2222,
    )
}

Important: Managed SSH destinations are a separate security domain from Network Policy. A configured destination’s resolved IP address and port remain reachable even if they overlap with denied_routes.

Internally, Chalk loads the configured keys into an ssh-agent and exposes its socket through both SSH_AUTH_SOCK and IdentityAgent in /root/.ssh/config. Sandbox processes can ask the agent to sign with any configured key, but cannot read the private-key material.

Important: Sandbox images containing any existing /root/.ssh are rejected, whether or not Managed SSH is configured for that sandbox.


​
Sandbox-to-sandbox and on-premise connectivity

Each sandbox gets its own network namespace with no shared listening sockets (see Multi-tenancy). Chalk provides no direct connection between sandboxes and no tunnel into an on-premise network. To exchange data between workloads, share it through a mounted Volume or an external service that both can reach over the network. To reach a private network that your cloud network already connects to, such as a VPC-peered range, use CIDR-level routes.

To connect to a sandbox from your own machine, for example with ssh or port forwarding, use Tunnels.