Network Limits
Network limits control outbound internet access from sandboxes. Each sandbox runs behind a firewall that restricts which external IP addresses and domains it can reach, preventing untrusted code from exfiltrating data or contacting arbitrary hosts.
Egress is open by default. You can restrict access per sandbox with a network policy: a default-deny allow list of IPv4 CIDR ranges and domains. Set the policy when you create a sandbox, then update or reset it while the sandbox exists.
Use cases
Section titled “Use cases”Network limits are used when code in a sandbox must not reach arbitrary hosts.
- Untrusted or generated code: code from users or agents runs with no outbound access, so it cannot send data out of the sandbox.
- Agents that call known services: a domain allow list lets an agent reach the model provider and the APIs it needs, and blocks every other host. The sandbox resolves only the names on the list.
- Dependency installs: the domains of package registries, such as PyPI or npm, are allowed, and the rest of the internet is blocked.
- Services at fixed addresses: an IPv4 range allow list admits services with known addresses, such as an internal API behind a static IP.
- Sensitive data: a sandbox that processes private files or datasets reaches only the hosts on its allow list. DNS is filtered as well, so a name that is not on the list does not resolve.
- Restrict after setup: a sandbox installs its dependencies with open egress, and its policy is updated to block all egress before untrusted code runs.
- Temporary access: the policy of a running sandbox is replaced to allow one more host for a task. A later update or a reset changes it again, and the revision history is kept.
- Checks before critical work: a request to an allowed host and a request to a blocked host confirm that the policy is in effect before work starts.
Create sandboxes with network restrictions
Section titled “Create sandboxes with network restrictions”Create a sandbox with a network policy. With blocking turned on, the sandbox can reach only the domains and IPv4 ranges on its allow lists. A domain allow list turns blocking on by itself, while an IPv4 allow list restricts egress only when blocking is on. Network policies apply to Linux sandboxes and macOS sandboxes; the examples below create Linux sandboxes, and a macOS create takes the same policy (network_policy, networkPolicy, or daytona sandbox create-macos --block-all, --allow-domain, --allow-cidr).
from daytona import Daytona
daytona = Daytona()
# Allow access to specific domains; everything else is deniedsandbox = daytona.v2.create( network_policy={"block_all": True, "allow_domains": ["example.com", "*.daytona.io"]},)| Parameter | Type | Description |
|---|---|---|
network_policy | NetworkPolicyInput | Sets the outbound network policy. It accepts only the block_all, allow_cidrs, and allow_domains keys. Any other key raises BadRequestError before the request is sent. If allow_domains is set, an omitted block_all becomes True. Setting block_all=False with allow_domains is rejected. |
import { Daytona } from '@daytona/sdk'
const daytona = new Daytona()
// Allow access to specific domains; everything else is deniedconst sandbox = await daytona.v2.create({ networkPolicy: { blockAll: true, allowDomains: ['example.com', '*.daytona.io'] },})| Parameter | Type | Description |
|---|---|---|
networkPolicy | NetworkPolicyInput | Sets the outbound network policy. It accepts only the blockAll, allowCidrs, and allowDomains fields. Any other field throws BadRequestError before the request is sent. If allowDomains is set, an omitted blockAll becomes true. |
# Allow access to specific domains; everything else is denieddaytona sandbox create \ --block-all \ --allow-domain example.com \ --allow-domain '*.daytona.io'| Flag | Type | Description |
|---|---|---|
--allow-domain | string | Adds one entry to the domain allow list. It implies --block-all. Repeat the flag for more entries. |
--allow-cidr | string | Adds one entry to the network allow list. Repeat the flag for more entries. |
- Only
daytona sandbox createanddaytona sandbox create-macosaccept the network flags. - The
create-from-snapshotcommand rejects the network flags as unknown flags.
# Allow access to specific domains; everything else is deniedcurl 'https://api.daytona.com/sandboxes' \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "network_policy": { "block_all": true, "allow_domains": ["example.com", "*.daytona.io"] } }'- The API rejects
"block_all": falsetogether withallow_domains. - Without
allow_domains,block_alldefaults tofalse. - The
allow_cidrsandallow_domainslists hold at most 128 entries combined.
Block all egress
Section titled “Block all egress”Create a sandbox with no outbound access, for code that needs no network. With blocking on and empty allow lists, the sandbox reaches no external address and DNS resolves no names.
from daytona import Daytona
daytona = Daytona()
# Deny all outbound trafficsandbox = daytona.v2.create(network_policy={"block_all": True})import { Daytona } from '@daytona/sdk'
const daytona = new Daytona()
// Deny all outbound trafficconst sandbox = await daytona.v2.create({ networkPolicy: { blockAll: true },})# Deny all outbound trafficdaytona sandbox create --block-all# Deny all outbound trafficcurl 'https://api.daytona.com/sandboxes' \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "network_policy": { "block_all": true } }'Allow IPv4 ranges
Section titled “Allow IPv4 ranges”Create a sandbox that reaches only specific IPv4 addresses and ranges, in the network allow list format. An IPv4 allow list applies only when blocking is on, so turn it on explicitly. Without a domain allow list, DNS resolves no names; connect by IP address.
from daytona import Daytona
daytona = Daytona()
# Allow one address and one subnet; everything else is deniedsandbox = daytona.v2.create( network_policy={"block_all": True, "allow_cidrs": ["208.80.154.232/32", "208.80.152.0/22"]},)import { Daytona } from '@daytona/sdk'
const daytona = new Daytona()
// Allow one address and one subnet; everything else is deniedconst sandbox = await daytona.v2.create({ networkPolicy: { blockAll: true, allowCidrs: ['208.80.154.232/32', '208.80.152.0/22'] },})# Allow one address and one subnet; everything else is denieddaytona sandbox create \ --block-all \ --allow-cidr 208.80.154.232/32 \ --allow-cidr 208.80.152.0/22# Allow one address and one subnet; everything else is deniedcurl 'https://api.daytona.com/sandboxes' \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "network_policy": { "block_all": true, "allow_cidrs": ["208.80.154.232/32", "208.80.152.0/22"] } }'Allow domains
Section titled “Allow domains”Create a sandbox that reaches only specific hosts by name, in the domain allow list format. The sandbox resolves and connects to the listed domains; a wildcard covers every subdomain of a host but not the host itself. The domain list turns blocking on by itself.
from daytona import Daytona
daytona = Daytona()
# Allow a host and all of its subdomains; everything else is deniedsandbox = daytona.v2.create( network_policy={"allow_domains": ["pythonhosted.org", "*.pythonhosted.org", "pypi.org"]},)import { Daytona } from '@daytona/sdk'
const daytona = new Daytona()
// Allow a host and all of its subdomains; everything else is deniedconst sandbox = await daytona.v2.create({ networkPolicy: { allowDomains: ['pythonhosted.org', '*.pythonhosted.org', 'pypi.org'] },})# Allow a host and all of its subdomains; everything else is denieddaytona sandbox create \ --allow-domain pythonhosted.org \ --allow-domain '*.pythonhosted.org' \ --allow-domain pypi.org# Allow a host and all of its subdomains; everything else is deniedcurl 'https://api.daytona.com/sandboxes' \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "network_policy": { "allow_domains": ["pythonhosted.org", "*.pythonhosted.org", "pypi.org"] } }'Network allow list format
Section titled “Network allow list format”Allow outbound traffic to specific IPv4 addresses and ranges. Each entry on the network allow list is a single address or a CIDR block, and the sandbox can reach it on any port. The list takes effect only when blocking is on. The SDKs and the API accept allow_cidrs with blocking off and ignore it; the CLI rejects --allow-cidr without --block-all or --allow-domain.
| Rule | Description |
|---|---|
| IPv4 only | IPv6 addresses and IPv6 CIDR blocks are not supported. |
| CIDR format | Use standard CIDR notation (A.B.C.D/N) with a prefix length in the range 0 to 32 (inclusive). A bare address such as 208.80.154.232 is also accepted. |
| No private ranges | Loopback, private, link-local, and carrier-grade NAT addresses return 400. |
| Max 128 entries | allow_cidrs and allow_domains combined. |
| Whitespace is ignored | Entries are trimmed, and empty entries are skipped. |
| Example | Value |
|---|---|
| Single IP | 208.80.154.232/32 (Wikipedia) |
| Subnet | 208.80.152.0/22 (Wikimedia) |
| Multiple networks | ["208.80.154.232/32", "199.16.156.103/32", "208.80.152.0/22"] |
Domain allow list format
Section titled “Domain allow list format”Allow outbound traffic to specific hosts by name. Each entry is a hostname, or a wildcard covering a hostname’s subdomains. A domain allow list turns blocking on by itself.
DNS is filtered as well: the sandbox resolves only the names on the list and refuses all others. A policy with only IPv4 entries, or with empty lists, resolves no names at all.
| Rule | Description |
|---|---|
| Domains only | Use hostnames such as example.com or api.openai.com. Do not include protocols, paths, ports, or query strings. |
| Wildcards supported | Prefix a domain with *. to allow its subdomains, for example *.daytona.io. A wildcard does not match the bare domain; add daytona.io as its own entry. |
| Case-insensitive | Entries are lowercased before matching. |
| Max 128 entries | allow_cidrs and allow_domains combined. |
| Example | Value |
|---|---|
| Single domain | example.com |
| Wildcard domain | *.daytona.io |
| Multiple domains | ["example.com", "*.daytona.io", "api.openai.com"] |
Operations
Section titled “Operations”Get network policy
Section titled “Get network policy”Get the network policy of a sandbox. The response carries two policies: the desired policy, which is the last one you set, and the applied policy, which the sandbox currently enforces. A change is applied in the background, so the two differ until the sandbox catches up. The response also carries an ETag that identifies the current revision; an update or reset must send it back.
from daytona import Daytona
daytona = Daytona()sandbox = daytona.v2.create()
policy = sandbox.get_network_policy()print(policy.status, policy.desired, policy.applied, policy.etag)The client method takes a sandbox or a sandbox ID:
policy = daytona.v2.get_network_policy("{sandboxId}")The response has the following fields:
| Field | Description |
|---|---|
desired | The last policy set on the sandbox, or None. |
desired_revision | The revision of desired. |
applied | The policy the sandbox enforces, or None. |
applied_revision | The revision of applied, or None. |
etag | The revision token to send back when you update or reset the policy. |
http_status | The HTTP status code of the response. |
status | The status is pending, applied, or failed. It is None for a sandbox that never had a policy. |
error | Explains why the policy failed when status is failed. |
import { Daytona } from '@daytona/sdk'
const daytona = new Daytona()const sandbox = await daytona.v2.create()
const policy = await sandbox.getNetworkPolicy()console.log(policy.status, policy.desired, policy.applied, policy.etag)The client method takes a sandbox or a sandbox ID:
const policy = await daytona.v2.getNetworkPolicy('{sandboxId}')The response has the following fields:
| Field | Description |
|---|---|
desired | The last policy set on the sandbox, or undefined. |
desiredRevision | The revision of desired. |
applied | The policy the sandbox enforces, or undefined. |
appliedRevision | The revision of applied, or undefined. |
etag | The revision token to send back when you update or reset the policy. |
httpStatus | The HTTP status code of the response. |
status | The status is pending, applied, or failed. It is undefined for a sandbox that never had a policy. |
error | Explains why the policy failed when status is failed. |
# --include prints the ETag response headercurl 'https://api.daytona.com/sandboxes/{sandboxId}/network-policy' \ --include \ --header 'Authorization: Bearer YOUR_API_KEY'{ "desired": { "version": 1, "block_all": true, "allow_cidrs": [], "allow_domains": ["example.com"] }, "desired_revision": 1, "applied": { "version": 1, "block_all": true, "allow_cidrs": [], "allow_domains": ["example.com"] }, "applied_revision": 1, "status": "applied", "error": null}The response has the following fields:
| Field | Description |
|---|---|
ETag | Holds the revision as "np-<revision>", for example "np-1". This value is a response header. |
status | The status is pending, applied, or failed. For a sandbox that never had a policy, status is null and the revision is 0. |
- This call requires the
read:sandboxespermission.
Update network policy
Section titled “Update network policy”Replace the whole network policy of an existing sandbox, with the same fields and rules as at create time. The sandbox must be running. The new policy is stored at once and applied in the background; get the policy to see when it takes effect.
Every update sends the ETag of the revision it replaces. If the policy changed since, the update is rejected instead of overwriting it; get the policy and retry with the new ETag.
from daytona import Daytona
daytona = Daytona()sandbox = daytona.v2.create()
current = sandbox.get_network_policy()updated = sandbox.update_network_policy( network_policy={"block_all": True, "allow_domains": ["pypi.org", "files.pythonhosted.org"]}, if_match=current.etag,)print(updated.status) # pending until the sandbox applies itThe client method takes a sandbox or a sandbox ID:
current = daytona.v2.get_network_policy("{sandboxId}")updated = daytona.v2.update_network_policy( "{sandboxId}", network_policy={"block_all": True, "allow_domains": ["pypi.org"]}, if_match=current.etag,)| Parameter | Type | Description |
|---|---|---|
network_policy | NetworkPolicyInput | Sets the full new policy, with the block_all, allow_cidrs, and allow_domains keys. This parameter is required. |
if_match | str | Passes the etag from the last get_network_policy() or update. This parameter is required. |
import { Daytona } from '@daytona/sdk'
const daytona = new Daytona()const sandbox = await daytona.v2.create()
const current = await sandbox.getNetworkPolicy()const updated = await sandbox.updateNetworkPolicy( { blockAll: true, allowDomains: ['pypi.org', 'files.pythonhosted.org'] }, { ifMatch: current.etag },)console.log(updated.status) // pending until the sandbox applies itThe client method takes a sandbox or a sandbox ID:
const current = await daytona.v2.getNetworkPolicy('{sandboxId}')const updated = await daytona.v2.updateNetworkPolicy( '{sandboxId}', { blockAll: true, allowDomains: ['pypi.org'] }, { ifMatch: current.etag },)| Parameter | Type | Description |
|---|---|---|
networkPolicy | NetworkPolicyInput | Sets the full new policy, with the blockAll, allowCidrs, and allowDomains fields. This parameter is required. |
options.ifMatch | string | Passes the etag from the last getNetworkPolicy() or update. This parameter is required. |
curl 'https://api.daytona.com/sandboxes/{sandboxId}/network-policy' \ --request PUT \ --header 'Content-Type: application/json' \ --header 'If-Match: "np-1"' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data '{ "block_all": true, "allow_domains": ["pypi.org", "files.pythonhosted.org"] }'- The
If-Matchheader is required and holds the ETag from the last get or update. - The response carries the same body as a get and the new
ETagheader. - This call requires the
write:sandboxespermission.
Reset network policy
Section titled “Reset network policy”Reset the network policy of a sandbox to open egress. A reset removes the restrictions but keeps the revision history, so it follows the same ETag rules as an update.
from daytona import Daytona
daytona = Daytona()sandbox = daytona.v2.create()
current = sandbox.get_network_policy()sandbox.reset_network_policy(if_match=current.etag)The client method takes a sandbox or a sandbox ID:
current = daytona.v2.get_network_policy("{sandboxId}")daytona.v2.reset_network_policy("{sandboxId}", if_match=current.etag)| Parameter | Type | Description |
|---|---|---|
if_match | str | Passes the etag from the last get_network_policy() or update. This parameter is required. |
import { Daytona } from '@daytona/sdk'
const daytona = new Daytona()const sandbox = await daytona.v2.create()
const current = await sandbox.getNetworkPolicy()await sandbox.resetNetworkPolicy({ ifMatch: current.etag })The client method takes a sandbox or a sandbox ID:
const current = await daytona.v2.getNetworkPolicy('{sandboxId}')await daytona.v2.resetNetworkPolicy('{sandboxId}', { ifMatch: current.etag })| Parameter | Type | Description |
|---|---|---|
options.ifMatch | string | Passes the etag from the last getNetworkPolicy() or update. This parameter is required. |
# A literal null body resets the policy to open egresscurl 'https://api.daytona.com/sandboxes/{sandboxId}/network-policy' \ --request PUT \ --header 'Content-Type: application/json' \ --header 'If-Match: "np-2"' \ --header 'Authorization: Bearer YOUR_API_KEY' \ --data 'null'Test network access
Section titled “Test network access”# Test HTTP connectivity to allowed addressescurl -I http://208.80.154.232
# Test HTTP connectivity to allowed domainscurl -I https://example.com