DocsNetwork and public URLs

Network and public URLs

Reach a port in a sandbox over HTTPS, limit which hosts the sandbox can call, and close its URLs to the public. Every port a sandbox listens on has a public HTTPS URL derived from the sandbox ID.

Every example reads IMPELLO_API_KEY from the environment; get a key at dashboard.impello.ai/keys.

Reach a port from the internet

getHost(port) returns the host for a port, <port>-<sandboxId>.sandbox.impello.ai. You supply the scheme and the path. There is no separate "expose port" call — start listening and the URL works.

import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')

await sandbox.commands.run('python3 -m http.server 8000 --bind 0.0.0.0', {
  background: true,
  timeoutMs: 0,
})

console.log(`https://${sandbox.getHost(8000)}`)

Bind the server to 0.0.0.0, not 127.0.0.1. Inside the sandbox, localhost is the sandbox itself, and a port that is only bound there refuses the proxied request.

timeoutMs: 0 (timeout=0 in Python) is there because the SDK stays attached to a command for 60 seconds by default, background: true included. Without it the handle throws after 60 seconds — the server itself keeps running and the URL keeps working, but the handle you held is gone. See Run commands.

What the URL carries

The proxy in front of a sandbox URL forwards HTTPS and WebSocket only: no SSH, no port-forwarded database socket and no other protocol reaches the sandbox from outside. Outbound traffic is a separate question, governed by allowOut and denyOut below. Run shell work with commands or an interactive terminal instead.

Restrict outbound traffic

Pass allowInternetAccess: false on create to cut the sandbox off from the internet. It behaves like denying 0.0.0.0/0: an IP or CIDR entry in allowOut still wins over it, so a sandbox created with both can reach the addresses you allowed. A domain in allowOut is different. It needs ALL_TRAFFIC in denyOut, as the next section shows.

import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base', { allowInternetAccess: false })

const result = await sandbox.commands.run('curl -sS https://example.com || true')
console.log(result.stdout)

For anything finer, use network.allowOut and network.denyOut (allow_out / deny_out in Python). Allowed entries always win over denied entries, so the usual shape is "deny everything, allow these". ALL_TRAFFIC is the 0.0.0.0/0 sentinel and is exported from both SDKs. A domain in allowOut requires ALL_TRAFFIC in denyOut. Without it, the create call fails with 400: When specifying allowed domains in allow out, you must include 'ALL_TRAFFIC' in deny out to block all other traffic., and allowInternetAccess: false does not count as that deny.

import { ALL_TRAFFIC, Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base', {
  network: {
    allowOut: ['api.github.com', '*.npmjs.org'],
    denyOut: [ALL_TRAFFIC],
  },
})

The create call takes these network options.

TypeScriptPythonDefaultWhat it does
allowInternetAccessallow_internet_accesstrueTop-level option on create. false is the same as denying 0.0.0.0/0
network.allowOutnetwork["allow_out"]unsetDestinations the sandbox may reach. Unset means all egress is allowed
network.denyOutnetwork["deny_out"]unsetDestinations the sandbox may not reach
network.rulesnetwork["rules"]unsetPer-host header transforms. Stored, but not applied to traffic; see Per-host rules
network.allowPublicTrafficnetwork["allow_public_traffic"]trueWhether sandbox URLs are reachable without authentication
network.maskRequestHostnetwork["mask_request_host"]${PORT}-sandboxid.sandbox.impello.aiThe Host the sandbox sees on proxied requests. ${PORT} is replaced with the port
network.egressProxynetwork["egress_proxy"]unsetSOCKS5 proxy the sandbox's outbound TCP is tunneled through. Unset sends the traffic out directly

egressProxy (egress_proxy) takes { address: 'host:port', username, password }. The credentials are optional, each is up to 255 bytes, and password needs username. Tunneling happens on the host after allowOut and denyOut, so code in the sandbox can neither see the proxy nor route around it. DNS and QUIC/HTTP3 travel over UDP and are not tunneled. Egress fails closed: an unreachable proxy stops the traffic rather than sending it out directly. An address that does not resolve, or resolves to a private range, is refused at create. getInfo returns the address and username, never the password. In TypeScript it needs @impello/sdk 0.3.2 or later.

If you set egressProxy, read Update at runtime before you change the policy on a running sandbox.

An allowOut entry can be a CIDR block (8.8.8.8/32), a bare IP address (8.8.8.8) or a domain (example.com, *.example.com). denyOut takes CIDR blocks and IP addresses only — a domain name in a deny list is not supported.

Both lists also accept a callback instead of an array. It receives the all-traffic sentinel and the hosts you registered under rules, which is how you say "allow exactly the hosts I wrote rules for".

There is no published outbound IP range for sandboxes and neither SDK exposes one, so authenticate the calls a sandbox makes into your own services instead of allowlisting an address.

Per-host rules

rules maps a host to a list of rules, at most one per host. A rule's transform.headers names headers to set on outbound HTTP and HTTPS requests to that host. The API accepts the rules, stores them and returns them from getInfo(), but the sandbox does not apply them: a request to a host with a rule leaves the sandbox without those headers. This was checked over HTTP and HTTPS on 2026-10-05. Do not rely on rules to inject a credential or to keep one out of the sandbox.

A create call with rules can also fail with 400: Network transform rules are not available for your team. when the feature is not enabled for your team.

Registering a host under rules does not allow egress to it. The host must also appear in allowOut. Naming hosts in allowOut without denyOut: [ALL_TRAFFIC] is refused. In TypeScript the allowOut callback receives { allTraffic, rules }, where rules is a Map. In Python it receives a context object with ctx.all_traffic and ctx.rules.

Restrict inbound traffic

To close the sandbox URLs to unauthenticated callers, set network.allowPublicTraffic to false. The create call returns the sandbox's traffic access token for the proxy in front of that one sandbox. Both SDKs expose it on the instance and type it as optional, so check it before reading it.

import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base', {
  network: { allowPublicTraffic: false },
})

console.log(sandbox.trafficAccessToken)

Neither SDK attaches the token to requests you build yourself, and neither one documents the form the proxy expects it in — header, query parameter or cookie. getHost(port) returns a host and nothing else, so it does not carry the token either. Until that form is published, read allowPublicTraffic: false as a switch that closes the URL rather than one that reopens it to callers holding the token.

secure is a different switch. It defaults to true and puts a token of its own on the sandbox's control channel — the one files, commands and pty use. Leaving secure at its default does nothing about a web server you start on port 8000; allowPublicTraffic is the option for that.

Update at runtime

updateNetwork changes the egress policy of a running sandbox without restarting it.

import { ALL_TRAFFIC, Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')

await sandbox.updateNetwork({
  allowOut: ['api.github.com'],
  denyOut: [ALL_TRAFFIC],
})

The update takes the egress fields this page covers: allowOut, denyOut, rules, egressProxy and allowInternetAccess (allow_out, deny_out, rules, egress_proxy, allow_internet_access in Python). It does not accept allowPublicTraffic; the update route carries no field for it, so set the inbound policy at create time.

egressProxy is part of the same replacement. An update that leaves it out stops tunneling and sends the sandbox's traffic out directly, even when you only meant to change the allow and deny lists, so repeat it in every update that should keep tunneling.

Both SDKs also expose it without an instance: Sandbox.updateNetwork(sandboxId, network) in TypeScript, Sandbox.update_network(sandbox_id, network) in Python.

Read the current policy

getInfo() returns the sandbox's network configuration as the server holds it, including whether internet access was explicitly set.

import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')

const info = await sandbox.getInfo()
console.log(info.allowInternetAccess)
console.log(info.network?.allowOut)
console.log(info.network?.allowPublicTraffic)

Next: Pause, resume and snapshots.