Skip to main content

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.

ListHoldsEffect
allow_outPublic IPv4 CIDR blocks, or blocks inside the sandbox's own subnetThe guest may connect to these addresses.
deny_outPublic IPv4 CIDR blocksThe guest may not connect to these addresses.
allow_inCIDR blocks inside the sandbox's own subnetThese addresses may connect to the guest.
allow_out_domainsDNS namesThe guest may connect to these names.
deny_out_domainsDNS namesThe 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.

warning

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_out and allow_in may 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 NXDOMAIN and 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 Host header. 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_out CIDR opens nothing; only the domain lists do. The proxy still refuses to dial an address in deny_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 ports capability, 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.

See also​