> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nanovm.dev.lithosai.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Limit outbound network access

> Let a sandbox reach only the hosts it needs, and change that while it runs.

Create a sandbox that can reach only the hosts you list:

```python theme={null}
from lithosbox import LithosBox

box = LithosBox()
sandbox = box.sandboxes.create(allow_egress=["pypi.org", "files.pythonhosted.org"])

sandbox.run("pip install -q requests").check()

blocked = sandbox.run(["python3", "-c", "import socket; socket.gethostbyname('example.com')"])
print(blocked.exit_code)
print(sandbox.info.allow_egress)
```

The install succeeds because pip downloads only from the two listed hosts.
`example.com` is not listed, so it does not resolve and the second command exits
with a nonzero code. The last line prints `['pypi.org', 'files.pythonhosted.org']`.

`allow_egress` requires SDK 0.10.0 or later. Over HTTP, send `allow_egress` in
[`POST /vms`](https://docs.nanovm.dev.lithosai.cloud/reference/http-api#create-a-sandbox).

## Write the list

| Entry | Example | Allows |
| - | - | - |
| Hostname | `pypi.org` | HTTPS and plain HTTP to exactly that name, on any TCP port. |
| Wildcard | `*.githubusercontent.com` | Names below the suffix, such as `raw.githubusercontent.com`. It does not match `githubusercontent.com` itself; list both when you need both. |
| IPv4 address or CIDR | `1.1.1.1`, `52.0.0.0/8` | Any protocol (TCP, UDP, ping) to those addresses. |

A list holds up to 256 entries. Hostnames are not case-sensitive, and
`sandbox.info.allow_egress` returns them in lowercase without duplicates.

These entries are refused with `InvalidRequestError` (`400`), and the message
names the entry:

* URLs, ports, and paths, such as `https://pypi.org` or `pypi.org:443`.
* IPv6 addresses.
* A bare `*`. To allow everything, leave `allow_egress` out, or pass
  `["0.0.0.0/0"]`, which means the same: no list.
* Private and reserved ranges, such as `10.0.0.0/8`, `192.168.0.0/16`, and
  `169.254.0.0/16`. Sandboxes cannot reach these with or without a list.

Passing a string instead of a list raises `ValueError` before any request.

## What a hostname covers

A hostname entry is matched against the name your client sends: the TLS server
name for HTTPS, or the `Host` header for HTTP. Package managers, `git` over HTTPS,
and HTTP APIs send it, so a hostname entry is enough for them.

| Tool | Hosts to list |
| - | - |
| pip | `pypi.org`, `files.pythonhosted.org` |
| npm | `registry.npmjs.org` |
| apt on Debian images | `deb.debian.org` |
| git over HTTPS | `github.com` |
| Hugging Face downloads | `huggingface.co`, `*.hf.co` |
| Docker Hub pulls (`runtime="vm"`) | `registry-1.docker.io`, `auth.docker.io`, `docker-images-prod.s3.dualstack.us-east-1.amazonaws.com` |

Downloads often redirect to a content host with a different name, and those names
can change. If a tool reports that it cannot resolve a name, add that name to the
list.

Other protocols do not carry a hostname: SSH, database protocols, mail, and raw
TCP. To reach those servers, list their IPv4 address or CIDR. Clone repositories
over HTTPS rather than SSH. HTTP proxy requests (`CONNECT`) are refused.

DNS answers only the listed names, with IPv4 addresses. Every other name fails to
resolve, including queries sent directly to a public resolver.

## Change the policy while the sandbox runs

`set_egress()` replaces the sandbox's whole outbound policy. Nothing is merged
with the previous list.

```python theme={null}
sandbox.set_egress(allow_egress=["api.example.com"])  # only this host
sandbox.set_egress(disable_internet=True)             # no outbound traffic
sandbox.set_egress()                                  # open internet access
```

Connections that the new policy no longer allows are closed. An archived sandbox
applies the policy when it is unarchived. Each call refreshes `sandbox.info`.

A common pattern is to install dependencies with the internet open, then limit
the sandbox before running code you do not trust:

```python theme={null}
worker = box.sandboxes.create()
worker.run("pip install -q requests").check()
worker.set_egress(allow_egress=["api.example.com"])
```

Over HTTP, use [`PUT /vms/{id}/egress`](https://docs.nanovm.dev.lithosai.cloud/reference/http-api#outbound-network-policy).

## Forks, snapshots, and archives

Forks, sandboxes restored from a snapshot, and unarchived sandboxes keep the list.
To give a restored sandbox a different list, pass it at creation:

```python theme={null}
snapshot = sandbox.snapshot(wait_durable=True)
restored = box.sandboxes.create(snapshot_id=snapshot.id, allow_egress=["pypi.org"])
```

Without `allow_egress`, the restored sandbox keeps the snapshot's list. Call
`set_egress()` on it to change or remove the list.

## When a connection is blocked

* A name that is not listed does not resolve. Tools report errors such as
  `Could not resolve host` (curl, git) or `EAI_AGAIN` (npm).
* An HTTPS or HTTP connection to an address whose name is not listed is reset.
* Other traffic to an address that is not listed is dropped, and the connection
  waits until the client's own timeout. Set finite connection timeouts.

Requests that reach your sandbox through a [public endpoint](https://docs.nanovm.dev.lithosai.cloud/guides/serve.md) are not
affected by the list.

An allowed host can still receive data from the sandbox. For example, allowing
`github.com` also allows a `git push` to it. List only the hosts the task needs.

When you are finished:

```python theme={null}
for s in (sandbox, worker, restored):
    s.delete()
box.snapshots.delete(snapshot.id)
box.close()
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.