Network Rules
Network rules control which destinations each binary in a sandbox can reach, and which requests it can send. They make up most of a typical policy. This page explains how OpenShell evaluates network rules, then gives example rules for common services and protocols that you can adapt.
How Network Rules Work
OpenShell checks every outbound connection from a sandbox against the
network_policies section of its policy, and denies any connection that no rule
allows.
Rule Structure
Each entry in network_policies is a rule. The entry’s key is the rule’s name,
and the rule contains two lists. endpoints lists the destinations the rule
allows, and binaries lists the executables that can connect to them. A rule
allows every listed binary to reach every listed endpoint.
For example, this rule is named example_api and lists two binaries and two
endpoints:
The rule allows these four combinations:
If wget should reach only api.example.com, put that binary and endpoint in a
separate rule.
Binary Matching
When you write a network rule, list the path of the executable that opens the
connection. This is not always the command you run. For example, pip is a
Python script, so the process that connects to PyPI is the Python interpreter,
and a rule for pip install must list the interpreter, not /usr/bin/pip.
OpenShell identifies each process by the real path of its executable, as the
kernel reports it, so list real paths rather than symlinks. For example, on
Ubuntu 24.04, /usr/bin/python3 is a symlink to /usr/bin/python3.12. To find
the real path, run readlink -f on the path inside the sandbox. When OpenShell
denies a connection, the sandbox log shows the path it identified. Allow PyPI
Downloads and Allow npm Installs
show rules that list interpreters.
A rule also applies to processes that a listed binary starts. For example, if a
rule lists an agent’s executable, tools that the agent launches can use the rule
too. A rule with an empty binaries list matches no binary and allows nothing.
OpenShell records a hash of each executable the first time it takes part in a connection, and denies later connections if the file at that path changes.
Connection and Request Checks
OpenShell checks network traffic in two stages:
- When a binary opens a connection, OpenShell checks the destination host and port and the binary against your rules. If no rule matches, OpenShell denies the connection.
- If the matching endpoint sets a request protocol, such as
rest, OpenShell also reads each request sent over the connection and checks it against the endpoint’s request rules. For example, withprotocol: rest, a rule can allowGETrequests to an API while blockingPOSTandDELETErequests.
The second stage is called request inspection, and an endpoint that uses it is
an inspected endpoint. The protocol field selects what OpenShell inspects:
When protocol is omitted, OpenShell allows any method and path. Unless the
endpoint sets tls: skip, it still terminates TLS and rejects HTTP requests
that are addressed to another host or have malformed paths, such as paths that
contain %2F. It relays traffic that is neither TLS nor HTTP without inspecting
it. For a protocol in which the server sends first, such as SMTP, set
tls: skip.
Use an inspected endpoint when the request, not only the destination, determines the risk, such as allowing reads but not writes on an API.
Enforcement
The enforcement field on an inspected endpoint decides what happens when a
request breaks the endpoint’s request rules:
enforceblocks the request. An HTTP client receives an OpenShellpolicy_deniedresponse.auditallows the request and logs the violation. This is the default.
Audit mode applies only to the endpoint’s allow and deny rules. OpenShell still rejects requests that it cannot process safely, such as malformed requests and requests addressed to another host.
Use audit to see what a new rule would block before you enforce it. Apply the
rule with enforcement: audit, run your workload, then look for violation
events in the sandbox log:
Do not filter this log with --level warn, which hides policy events. When the
log shows only the violations you expect, switch the endpoint to enforce,
because an endpoint in audit mode does not block requests that break its rules.
Overlapping Rules
Network rules are not an ordered firewall list. Several rules can match the same
connection or request, and every matching rule adds the access it allows. A
matching deny rule takes precedence over any allow, regardless of where either
appears in the file. If one matching rule inspects requests and another does
not, OpenShell inspects the connection, and the rule without protocol adds no
request access. Endpoints that can match the same host and port must also agree
on settings such as tls and allowed_ips, or OpenShell rejects the policy.
For example, suppose one rule allows GET /repos/** on a host and another rule
for the same host, port, and binary denies GET /repos/private/**. OpenShell
denies requests under /repos/private/. When you restrict access, review every
rule that could allow the request, including rules that providers contribute.
Allow Specific Methods and Paths shows a
deny rule.
Network Access and Credentials
A network rule that allows traffic to a destination does not allow OpenShell to send provider credentials there. OpenShell supplies a provider’s credentials only to the destinations that the provider’s profile or an explicit credential binding allows. When a request fails a credential check, correct the provider binding instead of widening the network rule. Refer to Provider Profiles for profile endpoints, and to Credential Fields for explicit bindings.
Examples
The following rules cover common services and protocols. Replace the example hosts, paths, and binaries with your own values, and make sure the sandbox contains the client executable.
When a single openshell policy update command can create a rule, the example
shows it. Otherwise, add the YAML under the network_policies section of a
complete policy file and apply it with openshell policy set, as described in
Replace the Complete
Policy.
After you apply a rule, verify the
change before testing
traffic.
Allow Read-Only API Access
Use this rule when a binary needs to read from an HTTP API but must not change
anything. The read-only preset permits GET, HEAD, and OPTIONS.
CLI
YAML
Verify an allowed GET and a denied POST:
The POST must return an OpenShell policy_denied response. read-only is an
HTTP method preset, not a guarantee that an upstream GET has no side effect.
Allow Specific Methods and Paths
Use explicit rules instead of an access preset when a binary needs specific
methods or paths, and deny_rules to block exceptions within them. This rule
lets the GitHub CLI make any API request for one repository, except requests to
its webhooks, which could send repository events to another destination. Replace
<org> and <repo>, and keep only the executable paths used by your image:
In request paths, * matches within one path segment, and ** written as a
whole segment matches across segments, so /repos/<org>/<repo>/** covers every
path under the repository but not /repos/<org>/<repo> itself. The deny rules
list the webhooks path and everything under it separately for the same reason.
The allow rule grants every HTTP method under the repository path, including
writes, so narrow it if the agent needs only specific operations. It does not
cover Git transport or GraphQL requests. To add a deny rule to an existing rule
without a complete policy file, use openshell policy update with --add-deny,
as described in Add or Remove Network
Access.
Apply the rule with a GitHub provider attached. Its profile must permit
credential use at api.github.com, and the token must authorize the repository
operations. Verify that gh api repos/<org>/<repo>/issues succeeds and that
gh api repos/<org>/<repo>/hooks returns an OpenShell denial. For a complete
Git push example, see Grant GitHub Push Access to a Sandboxed
Agent.
Allow PyPI Downloads
Package managers download packages with GET requests, so a read-only REST rule
allows installs while blocking uploads. pip is a Python script, so the rule
lists the real path of the Python interpreter, and any Python program in the
sandbox can use it. This rule lets pip and uv install packages from PyPI:
/usr/bin/python3.12 is the interpreter on Ubuntu 24.04. To find yours, run
readlink -f /usr/bin/python3 inside the sandbox. If your image uses another
interpreter, such as a uv-managed Python, list its real path or a glob that
matches it, such as /sandbox/.uv/python/*/bin/python3*. For a private package
index, replace the hosts with your index’s hosts.
For a sandbox with pip installed, verify that a download succeeds:
Allow npm Installs
Like pip, npm is a script, so the rule lists the Node.js interpreter. This
rule lets npm install packages from the public npm registry. It lists
/usr/bin/node, but official Node.js images install node at
/usr/local/bin/node, so adjust the path for your image:
npm requests scoped packages, such as @types/node, with an encoded slash in
the path. OpenShell rejects %2F in request paths unless the endpoint sets
allow_encoded_slash: true. npm also sends its security audit as a POST
request, which the read-only preset denies. Run npm install --no-audit, or
replace access: read-only with explicit rules that also allow the audit’s
POST requests. An endpoint cannot combine access and rules.
Verify that a scoped package lookup succeeds:
Restrict Destination Addresses
OpenShell blocks connections to private network addresses by default to prevent
server-side request forgery (SSRF). An endpoint with an exact hostname can still
reach the private addresses that its hostname resolves to, unless the endpoint
comes from an approved policy advisor
proposal. Loopback, link-local, and unspecified addresses, including the cloud
metadata address 169.254.169.254, are always blocked.
Use allowed_ips to limit the addresses an endpoint can reach, or to let a
wildcard host such as *.internal.example reach private addresses. This rule
allows read-only access to an internal API only at addresses in 10.20.0.0/16:
CLI
YAML
When an endpoint sets allowed_ips, every address its hostname resolves to must
fall within the list, including public addresses. Account for DNS rotation and
service failover before pinning addresses. All endpoints that share a host and
port must use the same allowed_ips list.
Verify that a request to the service succeeds. If it is denied, the sandbox log shows the resolved address and reason. Review both the hostname and the address instead of broadening the range when an address changes.
Allow WebSocket Messages
Use protocol: websocket to control WebSocket connections and the messages
that clients send over them. This template requires /usr/bin/node and a
WebSocket service you control. It permits the /v1/realtime upgrade and client
text messages on that upgraded path, while denying /v1/admin/**:
Use your Node client to test a successful upgrade and text message on
/v1/realtime, and a denied upgrade or message on /v1/admin/**. The path on a
WEBSOCKET_TEXT rule is the original upgrade path, not text-frame content.
OpenShell inspects complete client text messages. It does not inspect binary
frames or messages from the server. If the endpoint carries provider
credentials, OpenShell closes the connection with close code 1008 when the
client sends a binary frame, because it cannot check the frame for credentials.
Set allow_uninspected_credentials: true to relay binary frames anyway. Set
websocket_credential_rewrite: true only when client text messages contain
OpenShell credential placeholders that must be resolved.
When new network rules take effect, OpenShell closes open WebSocket connections,
so the client must reconnect. The client might receive close code 1012, or the
connection might close without a close frame.
Allow GraphQL Operations
Use protocol: graphql to allow or deny GraphQL operations. APIs such as
GitHub serve REST and GraphQL on the same host. A REST rule sees every GraphQL
request as POST /graphql, so it cannot tell a query from a mutation. Give each
API its own endpoint in one rule, and use the endpoint path field to send
/graphql requests to the GraphQL rules. This rule allows read-only REST
requests, GraphQL queries, and the createIssue mutation:
OpenShell selects the endpoint whose path most specifically matches the
request, so requests to /graphql use the GraphQL rules and all other paths use
the REST rules. An endpoint without path matches all paths. Endpoints that
share a host and port must agree on tls and allowed_ips.
For allow rules, every top-level field in an operation must match. A malformed or disallowed operation denies an entire batched request. GraphQL field names are application-specific, so review them against the service’s schema before you rely on them. For deny rules, persisted queries, and GraphQL over WebSocket, refer to GraphQL Rules.
With a GitHub provider attached, verify a REST read with gh api zen and a
query with gh api graphql -f query='{ viewer { login } }', then confirm that a
mutation other than createIssue returns an OpenShell denial.
Allow MCP Tools
Use protocol: mcp for sandbox-to-server MCP Streamable HTTP requests. This
template requires an MCP client that runs on the Python interpreter at
/usr/bin/python3.12, and a Streamable HTTP server you control. It allows
initialization, tool discovery, and read_status, while denying
delete_resource:
Verify initialization and read_status before confirming that delete_resource
returns a policy denial. Tool argument matching is not supported, so an allowed
tool can receive any arguments accepted by the server.
Omitting mcp.versions allows only the 2025-11-25 revision. To support an
older server, list the exact revisions it needs, as described in MCP Version
Selection. Server
responses and SSE messages are relayed without MCP policy parsing. Do not put an
MCP endpoint on the same host and port as an endpoint that uses a different
protocol. openshell policy update rejects this combination.
Allow Native TCP
Use protocol: tcp for a client that speaks a protocol other than HTTP, such
as a database client. OpenShell checks the hostname, port, and executable but
applies no request rules, so prefer an inspected protocol whenever the client
supports one.
On Debian and Ubuntu, /usr/bin/psql is a wrapper script that starts
/usr/lib/postgresql/<version>/bin/psql, so this rule lists that path with a
glob:
CLI
YAML
Do not add access, enforcement, request rules, or credential-rewrite fields
to a TCP endpoint. Verify that psql reaches the server, then verify that
another binary or port is denied at the connection boundary. Use a client
command that fails before application authentication when the rule is absent. A
database authentication error can show that the TCP connection reached the
server, but it does not prove that database credentials are correct.
OpenShell prepares DNS and TCP handling when the sandbox starts, so you can add a TCP endpoint to a running sandbox without recreating it.
Set tls: skip so that OpenShell relays the connection without examining it
when the client starts TLS as soon as it connects, when the client must complete
TLS itself, for example to present a client certificate, or when the server
sends first, as with SMTP, IMAP, and MySQL. Clients that send first and
negotiate TLS within their protocol, such as psql, do not need it. Refer to
Inspection Fields.
Prefer exact hostnames. A wildcard authorizes DNS queries for every matching name, which can expose a DNS-label exfiltration channel. An allowed hostname on shared infrastructure can also reach other tenants or virtual services behind the same connection, because OpenShell cannot check which service a non-HTTP payload addresses.
OpenShell answers DNS queries for the hosts in your rules with placeholder
addresses whose TTL is at most 30 seconds. This applies to every endpoint, not
only protocol: tcp. The sandbox applies no DNS search domains, so request each
hostname exactly as your rules name it. Clients must resolve the hostname again
before reconnecting. A client that caches an address indefinitely can fail
after it expires, even while the endpoint remains allowed. OpenShell returns an
empty answer to AAAA queries, so dual-stack clients must fall back to the A
record.
Next Steps
- Use Manage Sandbox Policies to apply, verify, and roll back policy changes.
- Use the Policy Schema Reference for protocol defaults, field constraints, and matcher semantics.
- Use Troubleshoot when a request is denied unexpectedly.