# Network and public URLs

Every port a sandbox listens on has a public HTTPS URL derived from the sandbox
ID. You choose what the sandbox may reach on the way out, and whether anyone
may reach it on the way in.

Every example reads `IMPELLO_API_KEY` from the environment; get a key at
[dashboard.impello.ai/keys](https://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.

<CodeTabs>
<Tab label="TypeScript">

```ts
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)}`)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create("base")

sandbox.commands.run(
    "python3 -m http.server 8000 --bind 0.0.0.0",
    background=True,
    timeout=0,
)

print(f"https://{sandbox.get_host(8000)}")
```

</Tab>
</CodeTabs>

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](/docs/sandbox/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](/docs/sandbox/commands) or
an [interactive terminal](/docs/sandbox/pty) instead.

<Callout kind="warning">
Every listening port is on the public internet by default, and the host is
derivable from the sandbox ID. Anything you start that has no authentication of
its own needs `allowPublicTraffic: false` below.
</Callout>

## Restrict outbound traffic

Pass `allowInternetAccess: false` on create to cut the sandbox off from the
internet. It behaves exactly like denying `0.0.0.0/0` — including the rule that
an `allowOut` entry still wins over that deny, so a sandbox created with both
can reach the hosts you allowed.

<CodeTabs>
<Tab label="TypeScript">

```ts
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)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create("base", allow_internet_access=False)

result = sandbox.commands.run("curl -sS https://example.com || true")
print(result.stdout)
```

</Tab>
</CodeTabs>

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.

<CodeTabs>
<Tab label="TypeScript">

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

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

</Tab>
<Tab label="Python">

```python
from impello import ALL_TRAFFIC, Sandbox

sandbox = Sandbox.create(
    "base",
    network={
        "allow_out": ["api.github.com", "*.npmjs.org"],
        "deny_out": [ALL_TRAFFIC],
    },
)
```

</Tab>
</CodeTabs>

<table>
  <thead>
    <tr><th>TypeScript</th><th>Python</th><th>Default</th><th>What it does</th></tr>
  </thead>
  <tbody>
    <tr><td>`allowInternetAccess`</td><td>`allow_internet_access`</td><td>`true`</td><td>Top-level option on create. `false` is the same as denying `0.0.0.0/0`</td></tr>
    <tr><td>`network.allowOut`</td><td>`network["allow_out"]`</td><td>unset</td><td>Destinations the sandbox may reach. Unset means all egress is allowed</td></tr>
    <tr><td>`network.denyOut`</td><td>`network["deny_out"]`</td><td>unset</td><td>Destinations the sandbox may not reach</td></tr>
    <tr><td>`network.rules`</td><td>`network["rules"]`</td><td>unset</td><td>Per-host header transforms. Does not allow egress on its own</td></tr>
    <tr><td>`network.allowPublicTraffic`</td><td>`network["allow_public_traffic"]`</td><td>`true`</td><td>Whether sandbox URLs are reachable without authentication</td></tr>
    <tr><td>`network.maskRequestHost`</td><td>`network["mask_request_host"]`</td><td>`${PORT}-sandboxid.sandbox.impello.ai`</td><td>The `Host` the sandbox sees on proxied requests. `${PORT}` is replaced with the port</td></tr>
  </tbody>
</table>

The table covers the options this page documents. The Python `network` dict
also accepts `egress_proxy`, which has no TypeScript equivalent; if you set it,
read [Update at runtime](#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 an ordered list of rules. A rule's `transform.headers`
are injected into matching outbound HTTP and HTTPS requests, replacing any
header of the same name, so the code in the sandbox never sees the credential.

Registering a host under `rules` does not allow egress to it. The host must
also appear in `allowOut`.

<CodeTabs>
<Tab label="TypeScript">

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

const token = process.env.UPSTREAM_TOKEN
if (!token) {
  throw new Error('UPSTREAM_TOKEN is not set')
}

const sandbox = await Sandbox.create('base', {
  network: {
    allowOut: ({ rules }) => [...rules.keys()],
    denyOut: [ALL_TRAFFIC],
    rules: {
      'api.example.com': [
        {
          transform: {
            headers: { Authorization: `Bearer ${token}` },
          },
        },
      ],
    },
  },
})
```

</Tab>
<Tab label="Python">

```python
import os

from impello import ALL_TRAFFIC, Sandbox

sandbox = Sandbox.create(
    "base",
    network={
        "allow_out": lambda ctx: list(ctx.rules.keys()),
        "deny_out": [ALL_TRAFFIC],
        "rules": {
            "api.example.com": [
                {
                    "transform": {
                        "headers": {
                            "Authorization": f"Bearer {os.environ['UPSTREAM_TOKEN']}",
                        },
                    },
                },
            ],
        },
    },
)
```

</Tab>
</CodeTabs>

In TypeScript the 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

Set `network.allowPublicTraffic` to `false` and the sandbox URLs stop answering
unauthenticated callers. 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.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

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

console.log(sandbox.trafficAccessToken)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create(
    "base",
    network={"allow_public_traffic": False},
)

print(sandbox.traffic_access_token)
```

</Tab>
</CodeTabs>

<Callout kind="warning">
The traffic access token is a credential for that one sandbox. Keep it on your
server and hand it out no more widely than you would the port itself.
</Callout>

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.

<CodeTabs>
<Tab label="TypeScript">

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

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

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

</Tab>
<Tab label="Python">

```python
from impello import ALL_TRAFFIC, Sandbox

sandbox = Sandbox.create("base")

sandbox.update_network(
    {
        "allow_out": ["api.github.com"],
        "deny_out": [ALL_TRAFFIC],
    }
)
```

</Tab>
</CodeTabs>

<Callout kind="warning">
The update replaces the whole egress configuration. A field you leave out is
cleared on the server, not merged, so send the rules you want to keep every
time.
</Callout>

The update takes the egress fields this page covers: `allowOut`, `denyOut`,
`rules` and `allowInternetAccess` (`allow_out`, `deny_out`, `rules`,
`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.

Python's `egress_proxy` 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.

<CodeTabs>
<Tab label="TypeScript">

```ts
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)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create("base")

info = sandbox.get_info()
print(info.allow_internet_access)
if info.network:
    print(info.network.get("allow_out"))
    print(info.network.get("allow_public_traffic"))
```

</Tab>
</CodeTabs>

Next: [Pause, resume and snapshots](/docs/sandbox/pause-and-snapshots).
