Network policy
Control exactly which hosts an agent's sandbox can reach, allow new ones from a run, and understand the rules no list can override.
Sandbox networks are allowlisted by default. An agent can reach the host-wide default list (package registries and source hosts), the hosts its project adds, its model (through SI Fabrik's relay) and SI Fabrik's tracker tools, and nothing else. GitHub and the main package registries are read-only. When something is blocked, the run says so and offers an Allow button.
What a sandbox can reach#
| Reachable | From |
|---|---|
| Package registries and source hosts | The host-wide default list: Mac Settings → Sandbox, or sandbox.network.default_allow in harnessd.yaml |
| Extra hosts for one project | sandbox.network.allow in WORKFLOW.md |
| Hosts from links | Package registries and git hosts, hosted services, and the linked projects' own registries |
| The model | A relay on the egress proxy (Docker) or the bridge (Cloudflare), which adds the credential and forwards to Anthropic, OpenAI or your gateway. Model hosts aren't on the allowlist: the sandbox can't reach them directly. |
| SI Fabrik's tracker tools | A port forward on the proxy; each run's MCP URL carries its own token |
localhost | Always: the app under test and dev servers |
The built-in default list:
registry.npmjs.org registry.yarnpkg.com repo.yarnpkg.com
pypi.org files.pythonhosted.org
proxy.golang.org sum.golang.org
crates.io index.crates.io static.crates.io
rubygems.org index.rubygems.org
repo.maven.apache.org repo1.maven.org
github.com codeload.github.com objects.githubusercontent.com raw.githubusercontent.comEdit it in Settings → Sandbox → Hosts every sandbox may reach, or press Restore SI Fabrik defaults. In Docker, github.com, registry.npmjs.org, registry.yarnpkg.com, crates.io and rubygems.org stay read-only whichever list they come from.
Add hosts for a project#
sandbox:
network:
mode: allowlist # default
allow:
- api.stripe.com # a host
- "*.internal.example.com" # subdomains
- search.example.com:9200 # a host and port
- 10.20.0.15 # an IPv4 address
use_defaults: true # include the host-wide list (default)Patterns are host, *.domain, host:port or an IPv4 address: no schemes, paths, @ or other wildcards. Set use_defaults: false to drop the host-wide list for this project.
Allow a blocked host from a run#
Blocked hosts are listed on each run in Live Agents as The sandbox blocked N hosts. Press Allow next to one to add it to the project's sandbox.network.allow.
Rules no list can override#
- Cloud metadata endpoints are always blocked:
169.254.169.254,169.254.170.2,fd00:ec2::254,100.100.100.200,metadata.google.internal,metadata.googandmetadata. They can't be added to a list. - Private addresses need an exact name. A host that resolves to a private, loopback or link-local address is refused unless it's listed by its exact name.
*.domainpatterns don't count. - The proxy pins the address it checked. It connects to the IP it validated and never resolves the name again, so DNS rebinding can't redirect the connection.
- TLS must name the host that was allowed. In allowlist mode, the server name in a tunnel's TLS handshake must be the host the sandbox asked the proxy for, so an allowed host can't be used to front another one. Only a host allowed by its exact IP address may omit it. Traffic to port 443 that isn't TLS is refused, and plain-HTTP requests are sent with the
Hostheader of the host that was checked. - Read-only hosts stay read-only (Docker). A project can't lift it. See below.
Read-only GitHub and package registries#
Docker sandboxes can read from github.com, registry.npmjs.org, registry.yarnpkg.com, crates.io and rubygems.org, but not write to them. For these hosts the egress proxy terminates TLS and checks each request:
| Allowed | Refused |
|---|---|
GET and HEAD | git push (git-receive-pack) |
git clone and git fetch (POST …/git-upload-pack) | npm publish, yarn publish, cargo publish, gem push |
npm audit (POST /-/npm/v1/security/advisories/bulk and /-/npm/v1/security/audits, …/audits/quick) | Git LFS batch requests (not supported yet), and any other request |
Agents never need to push: SI Fabrik saves the task branch and opens the pull request itself. See Branches and pull requests. A refused request answers 403 with the reason, and the run lists the host as github.com (read-only) among its blocked hosts, without an Allow button.
These hosts are reachable only on port 443 over TLS, and only under their own name: plain http:// is refused, and so is TLS for one of them sent to another host or an IP address.
The proxy's certificate#
So that the proxy can check requests to read-only hosts, every Docker sandbox trusts its certificate authority:
| In the sandbox | Contents |
|---|---|
/etc/harness-ca/ca.pem | The proxy's CA |
/etc/harness-ca/bundle.pem | The usual public roots plus the proxy's CA |
NODE_EXTRA_CA_CERTS | /etc/harness-ca/ca.pem |
SSL_CERT_FILE, CURL_CA_BUNDLE, GIT_SSL_CAINFO, REQUESTS_CA_BUNDLE, PIP_CERT, CARGO_HTTP_CAINFO | /etc/harness-ca/bundle.pem |
The browser image's Chromium gets the CA in its certificate store. A custom image works as long as its tools honor one of these variables; point any other tool at bundle.pem. The CA's key stays on the host, in the data folder's egress/ca/. See File locations.
How it works in Docker#
Each task gets its own --internal Docker network with no route out. Its agent, hooks, take-over terminal and linked services share it; other tasks' sandboxes can't reach them. The host has no address on these networks either, so nothing listening on the host or server (SSH, databases, other services) is reachable from a sandbox.
The only way out is SI Fabrik's egress proxy (harness-egress-<id>), set as HTTP_PROXY and HTTPS_PROXY, which checks every connection against that sandbox's policy. The proxy identifies a sandbox by the fixed IP SI Fabrik assigned it; containers have no capabilities, so they can't spoof another sandbox's address. The proxy also:
- relays model traffic, adding the real credential. See Agent credentials;
- forwards to SI Fabrik's tracker tools;
- serves previews to the host only, on loopback ports; sandboxes can't connect to them.
Opening a terminal or starting a linked service never restarts the proxy under running agents.
Task networks are /27 blocks inside a /20 in 172.30.0.0/16, chosen per data folder so two SI Fabrik instances on one machine don't overlap. Set HARNESS_SANDBOX_SUBNET to choose the /20 yourself. A task's network is removed when its last sandbox ends, and networks left by an earlier run are removed when SI Fabrik starts its first sandbox, including the harness-sandbox-<id> network all sandboxes shared in earlier versions. A leftover network that a container still uses is kept, and its block is skipped.
Cloudflare sandboxes apply the same allowlist rules in the bridge Worker. See Cloudflare Containers.
Open mode#
sandbox:
network:
mode: open # any host; not recommendedIn open mode a Docker sandbox may reach any host, but still only through the egress proxy: cloud metadata stays blocked, the read-only hosts stay read-only, and model credentials stay in the relay. Tools must use the HTTP proxy (HTTP_PROXY / HTTPS_PROXY); proxy-aware tools can tunnel to any port. Tools that ignore the proxy settings have no internet access.
A custom Docker network#
With docker.network set, the sandbox joins that network instead and gets no egress proxy: no allowlist, no read-only hosts, and the model credential is passed into the container. host.docker.internal points at the host. It's only possible on a Mac, for a project with Allow agents to run directly on this Mac turned on; harnessd and the Mac's default sandbox floor refuse it.
Hooks and terminals#
- Hooks in the sandbox get the agent's network policy during an attempt, and no network at all otherwise.
- Inspect terminals have no network. Take over terminals get the project's policy, on the task's network.
- Linked services get their own project's allowlist, on the task's network.