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.
| TypeScript | Python | Default | What it does |
|---|---|---|---|
allowInternetAccess | allow_internet_access | true | Top-level option on create. false is the same as denying 0.0.0.0/0 |
network.allowOut | network["allow_out"] | unset | Destinations the sandbox may reach. Unset means all egress is allowed |
network.denyOut | network["deny_out"] | unset | Destinations the sandbox may not reach |
network.rules | network["rules"] | unset | Per-host header transforms. Stored, but not applied to traffic; see Per-host rules |
network.allowPublicTraffic | network["allow_public_traffic"] | true | Whether sandbox URLs are reachable without authentication |
network.maskRequestHost | network["mask_request_host"] | ${PORT}-sandboxid.sandbox.impello.ai | The Host the sandbox sees on proxied requests. ${PORT} is replaced with the port |
network.egressProxy | network["egress_proxy"] | unset | SOCKS5 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.