Network Policy

--network-policy restricts where the requests, scriptling.wait_for, scriptling.net.websocket, scriptling.ai, and scriptling.mcp libraries may connect — the tool for letting scripts reach the internet without letting them reach your private network or cloud metadata endpoints. Enforcement happens at connect time: hostnames are resolved through the configured DNS servers, every resolved address is checked, and the connection is made to the validated address — so DNS rebinding, redirects, and IP-notation tricks don’t get through.

For scriptling.ai, the policy also covers remote_servers — the MCP servers an AI client can attach for tool use — so a script cannot reach an internal MCP endpoint by routing through ai.Client(..., remote_servers=[...]) instead of mcp.Client(...) directly.

scriptling --network-policy=policy.toml --disable-lib subprocess script.py

A missing or invalid policy file aborts startup rather than running scripts unrestricted. Combine the policy with --disable-lib subprocess in any mode so code cannot bypass it by shelling out to curl. A network policy constrains only the libraries wired to it; it is not a process-level network sandbox.

With a policy active, these address categories are blocked by default:

  • Loopback (127.0.0.0/8, ::1)
  • Link-local (169.254.0.0/16 including cloud metadata endpoints, fe80::/10)
  • Private (10/8, 172.16/12, 192.168/16, fc00::/7)
  • Unspecified (0.0.0.0, ::) and multicast addresses
  • URLs that name an IP directly (e.g. http://10.0.0.1/)

Policy File Reference

A TOML file; every key is optional.

# Require https:// and wss:// URLs only
https_only = false

# Permit URLs that name an IP directly (http://1.2.3.4/). Blocked by
# default unless the address is already granted by allow_cidrs; every
# literal still faces the address-category and deny_cidrs rules.
allow_ip_literals = false

# Permit loopback addresses (e.g. for local testing)
allow_loopback = false

# Permit private network addresses (10/8, 172.16/12, 192.168/16, fc00::/7)
allow_private_ips = false

# Host allowlist: when set, ONLY these hosts may be contacted. Listed
# hosts are trusted — they may resolve to internal addresses. An exact
# name matches itself; a leading dot matches the domain and all subdomains.
allow_hosts = ["api.example.com", ".internal.corp"]

# Host denylist: always wins, even over the allowlist. Same syntax.
deny_hosts = ["tracker.example"]

# Address range exceptions. allow_cidrs overrides the built-in address
# blocks (how you grant one slice of your LAN); deny_cidrs wins over
# everything.
allow_cidrs = ["10.1.0.0/16"]
deny_cidrs  = ["10.66.0.0/16"]

# Resolve hostnames through these servers instead of the host's resolver
# (plain DNS, port 53). One resolver then serves every script network path,
# including scriptling.net.resolve, so lookups and connections always agree.
dns_servers = ["1.1.1.1", "8.8.8.8:53"]

# Cap each HTTP request end to end - dial, TLS, redirects, reading the
# body - e.g. "30s" or "2m". Off by default: requests run as long as
# their own per-request timeout allows, which long-running calls such as
# LLM APIs need.
client_timeout = "30s"

Common recipes:

# Internet-only: no internal access at all (the default policy — an empty file)
# API allowlist: scripts may call one API and nothing else
allow_hosts = ["api.example.com"]
# One slice of the LAN granted, https only. IP-literal URLs inside this
# range are accepted without setting allow_ip_literals.
https_only = true
allow_cidrs = ["10.1.0.0/16"]

Note that in the CLI, custom DNS always comes with the policy’s address checks — a policy file cannot turn them off. Embedding hosts that want nameservers without any blocking can construct a resolver-only configuration in Go (see AllowAll in the library registration guide).

Go hosts configure the same policy in code — see the library registration guide for the netsecurity.Config reference, and the security guide for the broader sandboxing model.