How sandbox networking works
A sandbox starts with no network access in either direction. This page explains the policy that opens it, how domain rules are enforced, why egress needs a NAT gateway, and how you reach a port inside a sandbox without opening it to the network.
Deny by default
Each sandbox has its own network port in your virtual private cloud (VPC). For VPCs, subnets, and NAT gateways, see How networking works.
A sandbox created with no network policy receives nothing over the network
except the ICMP messages path MTU discovery needs. It sends nothing either, with two exceptions that every policy keeps open: DNS
queries to the platform's resolvers on port 53, and TCP port 80 on the
metadata address 169.254.169.254, where the guest reads its own identity.
You do not need the network to use a sandbox: exec, files, code runs, port tunnels, and the desktop all use a separate host-to-guest channel. See How sandboxes work.
The policy document
The network policy is one document with five lists. Each list is empty by default.
| List | Holds | Effect |
|---|---|---|
allow_out | Public IPv4 CIDR blocks, or blocks inside the sandbox's own subnet | The guest may connect to these addresses. |
deny_out | Public IPv4 CIDR blocks | The guest may not connect to these addresses. |
allow_in | CIDR blocks inside the sandbox's own subnet | These addresses may connect to the guest. |
allow_out_domains | DNS names | The guest may connect to these names. |
deny_out_domains | DNS names | The guest may not connect to these names. |
Each CIDR list holds at most 16 entries, and each domain list at most 32. An
IPv6 entry in any CIDR list is refused with 412 SandboxEgressNotPermitted,
because no guest has an IPv6 path. An allow_in entry outside the sandbox's
own subnet is refused the same way: a sandbox has no public address, so only
its own subnet can reach it.
Traffic is denied unless a list allows it. The two kinds of list settle a conflict in opposite ways. Where a CIDR allow and a CIDR deny both match, the allow wins. Where a domain allow and a domain deny both match, the deny wins. A refused connection fails at its first packet.
Once a policy has any domain rule, in either domain list, an allow_out
CIDR no longer opens TCP ports 80 and 443. Those two ports follow the domain
lists alone, so an address you allowed by CIDR is unreachable on them unless
its name is in allow_out_domains. A deny_out CIDR still applies on those
ports, through the proxy described below. Every other port keeps following
the CIDR lists.
A leading *. in a domain entry matches every name below it. *.github.com
matches api.github.com but not github.com, so give both when you need
both. An allow entry must name a domain below a top-level domain, so *.com
is refused.
A write replaces the whole document. A list you leave out is cleared. A policy change closes open connections that the new policy no longer allows.
Ranges that are always refused
Some destinations are refused whatever the policy says. An entry in any list,
deny_out included, that names a private (RFC 1918) range, carrier-grade NAT
shared space, loopback, link-local, or multicast addresses, or any IPv6 block, is
refused with 412 SandboxEgressNotPermitted. Blocks inside the sandbox's own
subnet are the exception for allow_out and allow_in. Allow entries are also
checked against the rest of the reserved address space and refused the same way.
Three exceptions cut through these ranges:
- DNS to the platform's resolvers, always open.
- The metadata service at
169.254.169.254, TCP port 80, always open. - Blocks inside the sandbox's own subnet, which
allow_outandallow_inmay name. They reach your own guests on that subnet and nothing else.
Egress goes through the NAT gateway
A sandbox reaches the internet only through a NAT gateway in its VPC. The VPC needs a NAT gateway and a default route to it. Without a gateway, the API refuses a policy with domain rules, and the error says domain rules need a NAT gateway. It accepts CIDR allows to public addresses, but they carry no traffic until the VPC has a gateway. IPv6 egress is off.
Domain rules are enforced in the NAT gateway, because every packet that leaves the VPC already passes through it:
- Every DNS query from the guest goes to a filtering resolver. An allowed name
gets an answer. Any other name gets
NXDOMAINand is never sent upstream. - Connections to TCP ports 80 and 443 go through a proxy that reads the TLS
server name (SNI) or the HTTP
Hostheader. The proxy connects only when that name is allowed, and resets the connection otherwise. TLS is never decrypted. - On ports 80 and 443, an
allow_outCIDR opens nothing; only the domain lists do. The proxy still refuses to dial an address indeny_out, so a CIDR deny wins over a domain allow. - Rules apply to names, not to addresses. A guest cannot reach a name that is not allowed by connecting to a shared CDN address.
Domain egress is behind the egress_domains feature gate, which an operator
turns on per tenant. CIDR egress needs no gate.
An idle egress connection through the NAT gateway closes 10 to 20 minutes after its last read.
Reach a port with a tunnel
To reach a service in a sandbox, you open a port-forward tunnel. The tunnel goes from your machine to the Cloud API, and from there to the guest over the host-to-guest channel. It does not use the sandbox's network port.
This has three consequences:
- Only callers with your credential, or a session token with the
portscapability, can use the tunnel. - The service in the guest can listen on
127.0.0.1. It does not need to listen on the network. - A tunnel works when the network policy denies all inbound traffic.
A tunnel carries any TCP service: a web app, a database, or a debugger. SSH
and Visual Studio Code Remote-SSH use the same tunnel through
tritonctl sandbox ssh-config. The desktop's RDP connection uses its own
tunnel. Tunnels are behind the ports feature gate, which is on by default.
Preview URLs are not enabled yet
Browser preview URLs give each port its own HTTPS name, with a per-sandbox token. They are not enabled on Triton Cloud yet. Use a port-forward tunnel to reach a port.
While previews are off, tritonctl sandbox url, tritonctl sandbox ports open,
and the SDK calls get_host, get_url, and ports.open stop with this
message:
previews are not enabled on this cloud; use port-forward (the `preview` sandbox feature is off)
When the API refuses ports open, tritonctl adds this hint line:
`tritonctl sandbox port-forward <sandbox> <port>` reaches the port from this machine instead
The Python SDK raises PreviewsDisabledException, and the TypeScript SDK
throws PreviewsDisabledError. tritonctl sandbox capability lists ports
and preview_urls as refused. Port-forward tunnels keep working.