Sandboxes
Restrict a sandbox's outbound network access, give it managed SSH keys, and connect it to other workloads.
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 *:
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.
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.
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 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/.sshare rejected, whether or not Managed SSH is configured for that sandbox.
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.