---
title: "Restrict which hosts and IPs your app can reach"
description: "Allow only named hosts, IPs and CIDR ranges, roll the rule out from observe to enforce, and export it as Kubernetes or AWS security group rules."
section: "guides"
status: "Building for 0.3"
spec: "network"
canonical: "https://mainsdk.dev/docs/guides/restrict-network"
updated: "2026-10-28"
---

# Restrict which hosts and IPs your app can reach

> Allow only named hosts, IPs and CIDR ranges, roll the rule out from observe to enforce, and export it as Kubernetes or AWS security group rules.

Status: Building for 0.3 · Canonical: https://mainsdk.dev/docs/guides/restrict-network · Updated: 2026-10-28

Your app will reach only the hosts, IPs and ranges you name. Every other outbound call is blocked in process and reported with the file and line that made it. You will write an allowlist, run it in observe mode to see what the app really calls, switch it to enforce, and export the same list as a firewall rule for a second, independent wall. It takes about 15 minutes.

> **Note:** `network` is in 0.3 (spec `network`) and runs from the repository; it is not published to npm. Enforcement stays free on every plan: an app protecting itself is never a paid action.

## You need

- A MainSDK app with a `main.yaml` (see [Thirty-minute path](https://mainsdk.dev/docs/getting-started/thirty-minutes)).
- The list of outside services your app calls. You don't need the hosts of your drivers: each driver declares its own.

## 1. Start in observe mode

```yaml title="main.yaml"
network:
  allow:
    - api.partner.com      # exactly this host, not its subdomains
    - "*.acme.dev"         # subdomains of acme.dev only, not acme.dev itself
    - 10.20.0.0/16         # an IPv4 range, such as your VPC
    - 8.8.8.8              # one IP
    - "2001:db8::/32"      # an IPv6 range
  default: deny
  mode: observe            # observe, then warn, then enforce
```

In `observe`, every call still succeeds, and each call that enforce would block is written to the decision log as a would-block.

## 2. Call outside APIs through the SDK

```ts title="lib/rates.ts"
import { http } from "mainsdk";

export async function loadRates() {
  const res = await http.fetch("https://api.partner.com/v1/rates");
  return res.json();
}
```

`http.fetch` is the one client every driver and your own code use, so every outbound call passes the same check. `mainsdk check` fails on raw sockets, other HTTP libraries and a bare `fetch` outside the SDK (`MAIN-NET-0001`), so nothing slips past it.

The helpers `http.get`, `http.post`, `http.json`, `http.events` and `http.websocket` cover the other common calls, and `http.assert` checks a database URL or git remote before a client connects. Each one is checked the same way. Programs such as `git` or `curl` run through `system.run`, which checks every URL in their arguments.

### What is covered

| Kind of call | How to make it | Checked by |
| --- | --- | --- |
| Web requests (`fetch`, `get`, `post`, `json`) | `http.fetch`, `http.get`, `http.post`, `http.json` | `network.allow` at call time; `json` also refuses non-2xx answers (`MAIN-NET-0008`) |
| WebSockets | `http.websocket(url)` | the host, before the socket opens |
| Server-sent events | `http.events(url)` | `network.allow` at call time |
| Database URLs and git remotes | `http.assert(url)`, then connect | the host, before the client connects |
| Programs that reach the network (`git`, `curl`, `wget`) | `system.run(cmd, args)`, declared under `permissions.processes` | every URL in the arguments |
| Local files | `disk` (declared under `permissions.disk`) | not the network; see [Declare what your app uses](https://mainsdk.dev/docs/guides/declare-what-your-app-uses) |
| User uploads | `files` | the upload driver's own hosts, which the driver declares |

Local folders are not network calls, so `network.allow` does not govern them. Read [Declare what your app uses](https://mainsdk.dev/docs/guides/declare-what-your-app-uses) for folders, facts and programs.

## 3. Find every host your code uses

```bash
npx mainsdk discover
```

`discover` reads your source and lists every host the code connects to, with the file and line. A host that `network.allow` does not list is marked `MISSING`, and the output ends with the lines to add. Add the hosts you expect; remove the code that calls the ones you don't.

`npx mainsdk info --json` shows the same list under `uses`, next to the destinations each driver declares, with each would-block's rule and calling file and line. `npx mainsdk discover --json` prints the full manifest for scripts and agents.

To keep the list with the build, write it to a file:

```bash
npx mainsdk discover --out build/manifest.json
```

Run it in CI or in your build step. The manifest records every host, folder, fact and program the app uses, so a reviewer or a platform rule can check it without running the app.

`mainsdk check` fails with `MAIN-NET-0007` for any host the code uses that `network.allow` doesn't list, so run discovery again after you add a call.

## 4. Enforce

```yaml title="main.yaml"
network:
  mode: enforce
```

A call to any other host now fails with `MAIN-NET-0002`. The SDK also checks:

- **DNS answers.** A declared name that resolves to a private range you didn't list, such as `127.0.0.1`, is blocked as rebinding.
- **Redirects.** A redirect from an allowed host to an undeclared one is refused with `MAIN-NET-0003`.

The decision is made in your app, with no network round trip. If the policy layer fails, the last good list keeps applying (see [Fail static](https://mainsdk.dev/docs/concepts/fail-static)).

## 5. Add a second wall at the platform

An in-app check can't stop code that never loads the SDK, such as a native module. Write the same list into your platform's own egress controls:

```bash
npx mainsdk network export --to k8s          # Kubernetes NetworkPolicy
npx mainsdk network export --to aws-sg       # AWS security group egress rules
```

Exports for Vercel and Cloudflare are planned. For the list above, `--to k8s` writes egress rules for `10.20.0.0/16` and `8.8.8.8/32`. Platform rules match IPs, so names are resolved when you export; re-export when a provider's IPs change. Add `--discovered` to include hosts the code uses that `network.allow` does not list yet.

## Verify

```bash
npx mainsdk check --env prod
```

A pass exits 0 with no `MAIN-NET` finding. In a test, call an undeclared host and expect `MAIN-NET-0002`.

## If it fails

| Code | Cause | Fix |
| --- | --- | --- |
| `MAIN-NET-0001` | Code outside a driver imports `node:net`, `axios` or another client, calls `fetch` directly, opens `new WebSocket` or `EventSource`, or runs `git`, `curl` or `wget` as a child process. | Call through `http` or a driver; run programs with `system.run`. |
| `MAIN-NET-0002` | A call went to a host that is neither in `network.allow` nor declared by a driver. | Add the host if you expect the call; otherwise remove the code. |
| `MAIN-NET-0003` | An allowed host redirected to an undeclared one. | Declare the target host, or call it directly. |
| `MAIN-NET-0004` | A third-party API offers less than TLS 1.2. | Ask the provider to upgrade; meanwhile add an exception with an expiry. |
| `MAIN-NET-0005` | An entry in `network.allow` is not an IP, a CIDR range, an exact host or a `*.domain`. | Fix the entry; the error names it. |
| `MAIN-NET-0006` | A request was redirected more times than the SDK follows. | Call the final URL directly, or fix the redirect loop. |
| `MAIN-NET-0007` | `mainsdk check` found a host the code uses that `network.allow` doesn't list. | Add the host, or remove the call. `npx mainsdk discover` prints the lines. |
| `MAIN-NET-0008` | `http.json` or `http.events` got a non-2xx answer, or a body that is not JSON. | Check the URL, the credentials and the service status, or read the response with `http.fetch`. |

## For agents

```text
Goal: restrict this app's outbound network to the hosts it needs.
Read: docs/specs/network.md, this page, main.yaml.
Touch only: main.yaml, files that call outside APIs.
Do: add network.allow with mode: observe; replace raw fetch, axios, sockets and child-process curl or git with
http helpers or system.run; run npx mainsdk discover and add only the expected hosts; ask the owner before switching to enforce.
Run npx mainsdk discover again after every new outbound call.
Verify: npx mainsdk check --json --agent
Never add a wildcard that covers a whole provider or "*" to make a call pass.
```

## What comes later

- **Across many apps.** Fleet pushes one signed allowlist to every app, can tighten an app's list but never loosen it, and keeps a live map of every destination (phase 4, specs `fleet-control` and `fleet-observe`).
- **Inbound traffic.** Secure headers, CSRF and CORS are in [inbound protection](https://mainsdk.dev/docs/guides/sanitize-and-protect-inbound), and rate limits in [`limits`](https://mainsdk.dev/docs/interfaces/limits). This page is outbound only.

## Next

- [Declare what your app uses](https://mainsdk.dev/docs/guides/declare-what-your-app-uses): hosts, folders, facts and programs in one place
- [See every call, decision and change your app makes](https://mainsdk.dev/docs/guides/see-what-your-app-does)
- [Network permissions](https://mainsdk.dev/docs/interfaces/network), the interface reference
