MainSDK
Search⌘K
Interfaces

Platform, OS and network

Network permissions

network

Declare the hosts your app may call; every other outbound call is blocked and reported, in CI and in production.

Building for 0.3ships as a beta APIPhase 1 · Core, public betaSpec networkFree in your app, Apache-2.0

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

network makes your app declare who it may talk to, like a mobile app's permissions. Anything not on your list in main.yaml, or declared by a driver, is blocked and reported.

At a glance

network:
  allow:
    - api.partner.com
    - "*.pinecart.dev"
  default: deny
  mode: enforce
  http:
    timeout: 10s
    connect: 3s
    retries: { attempts: 3, backoff: 200ms, max: 5s, deadline: 30s }
    breaker: { failures: 50%, window: 30s, min: 10, cooldown: 15s }
    hosts:
      api.partner.com: { timeout: 3s, retries: { attempts: 5 } }
exceptions:
  - { rule: net.default_deny, reason: "partner VPN", owner: "@pinecart/platform", expires: 2026-11-30 }

Calls

  • http.fetch(url, init)
  • http.get(url, init)
  • http.post(url, body, init)
  • http.json(url, init)
  • http.events(url, init)
  • http.websocket(url, protocols)
  • http.assert(url)
  • network.destinations()
  • exportAwsSg(allow, opts)
  • exportK8s(allow, opts)
  • isPrivateIp(ip)
  • parseRule(text)
  • createHttp(net)
  • network.assert(target)
  • network.mode
  • network.rules
  • network.decisions
  • network.entries
  • network.breakerSlots()
  • network.breakerStates()
  • network.decide(url, reason?, caller?)
  • network.decidePublic(url, caller?)
  • network.fetch(input, init?)
  • network.fetchPublic(input, init?)
  • network.matchHost(host)
  • network.matchIp(ip)
  • network.reload(opts)
  • network.settingsFor(host, call?)
  • network.trim()

Guarantees

Whatever the driver.

  • Deny by default; undeclared hosts blocked and reported
  • DNS rebinding and redirects to undeclared hosts refused
  • Every helper (get, post, json, events, websocket) passes the same check as http.fetch
  • http.json and http.events refuse non-2xx answers and non-JSON bodies with MAIN-NET-0008
  • Every outbound call has a timeout; retries and a per-host breaker are built in, with no hand-rolled loops
  • Free on every plan

Drivers

1 drivers. Select one to see its line in main.yaml.

main.yamlBuilt in (default)
network:
  driver: builtin

Change this one line to swap providers; your calls stay the same.Built-in and local drivers integration

Codes

CodeWhere it appearsMeaning
MAIN-NET-0001Checkmainsdk check, before deployRaw socket, another HTTP client or direct fetch outside the SDK: new WebSocket, EventSource, node:http, node:https, node:dgram, ws, or git, curl or wget run as a child process
MAIN-NET-0004Checkmainsdk check, before deployA third-party API offers less than TLS 1.2 (TLS 1.3 is the minimum for endpoints your app serves, under MAIN-NET-*)
MAIN-NET-0007Checkmainsdk check, before deployThe code connects to a host that network.allow doesn't list; the finding names the call (http, websocket, event-stream, database, git)
MAIN-NET-0040Checkmainsdk check, before deployThe code calls a non-local host over plain http://
MAIN-NET-0041Checkmainsdk check, before deployTLS below 1.2 is forced, or certificate checks are turned off
MAIN-NET-0050Checkmainsdk check, before deployInfo.plist or app.json turns off App Transport Security with NSAllowsArbitraryLoads, so the app can load plain http
MAIN-NET-0051Checkmainsdk check, before deployAndroidManifest.xml or app.json allows cleartext traffic (usesCleartextTraffic), so the app can send data over plain http
MAIN-NET-0002RuntimeError responses and logsCall to a host not in network.allow (or not declared by a driver)
MAIN-NET-0003RuntimeError responses and logsRedirect to an undeclared host refused
MAIN-NET-0005RuntimeError responses and logsAn entry in network.allow is not an IP, a CIDR range, an exact host or a *.domain (for example a prefix length out of range)
MAIN-NET-0006RuntimeError responses and logsA request was redirected more times than the SDK follows, or redirect: "error" met a redirect
MAIN-NET-0008RuntimeError responses and logshttp.json or http.events got a non-2xx answer (429 when the provider rate-limits, else 502), or JSON that didn't parse; the body is never echoed
MAIN-NET-0009RuntimeError responses and logsAn outbound call timed out: the connect or total timeout of its last attempt, or its deadline, passed (504; the error names which)
MAIN-NET-0010RuntimeError responses and logsThe circuit breaker for the host is open, or half-open with its probe in flight; no request was sent (503, retryable, retryAfter is the rest of the cooldown)
MAIN-NET-0011RuntimeError responses and logsAn outbound call failed with a network error (refused, reset, DNS failure) on its last attempt (502; states the number of attempts)
MAIN-NET-0012RuntimeError responses and logsA value under network.http is invalid: a bad duration, attempts outside 1 to 10, failures outside 1 to 100 %, min below 1, or an unknown key (fails at load; names the key)

Features

Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.

FeaturePhaseMilestoneSpec ticket
Allowlist in main.yaml with driver-declared hostsPhase 10.3network#schema
Enforcement in the SDK network layerPhase 10.3network#chokepoint
DNS and redirect checksPhase 10.3network#dns-redirects
Blocked calls reportedPhase 10.3network#report
Check against raw sockets and other HTTP clientsPhase 10.3network#build-check
Export as platform firewall rulesPhase 10.3network#export
Helpers for GET, POST, JSON, server-sent events and WebSocketsPhase 10.3network#helpers
http.assert for database URLs and git remotes before connectingPhase 10.3network#helpers
Discovery of every host the code uses (mainsdk discover)Phase 10.3network#discover
Discovered hosts written as a build-time manifest (discover --out)Phase 10.3network#discover
Timeouts per attempt and per call, with a connect timeoutPhase 10.3network#timeouts
network.http settings per host, with field-by-field overridesPhase 10.3network#http-config
Retries for idempotent calls, with backoff and Retry-AfterPhase 10.3network#retries
Per-host circuit breakerPhase 10.3network#breaker
Every attempt and breaker change in the decision logPhase 10.3network#attempt-log
Driver defaults for timeouts and idempotency keysPhase 10.3network#driver-defaults
Measured overhead against the performance budgetPhase 10.3network#reliability-overhead

MainSDK Cloud

Everything above is free in your app. Cloud adds these on top, by plan:

PlanWhat it adds
FleetEgress allowlists, approved providers and models enforced across every app

Quick start

main.yaml
network:
  allow:
    - api.partner.com
  default: deny
  mode: enforce

lib/rates.ts

import { http } from "mainsdk";

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

A call to a host that is not on the list, and not declared by a driver you use, is blocked with MAIN-NET-0002.

Configuration

Every key below lives under network:.

main.yaml
network:
  allow:
    - 8.8.8.8            # one IP
    - 10.20.0.0/16       # a range (IPv4 or IPv6 CIDR)
    - google.com         # exactly this domain, not its subdomains
    - "*.pinecart.dev"   # subdomains of pinecart.dev only, not pinecart.dev itself
  default: deny          # everything else is blocked and reported
  mode: enforce          # observe | warn | enforce
KeyTypeWhat it does
allowlistYour own destinations: IPs, CIDR ranges, exact domains, or *.domain wildcards
defaultstringWhat happens to anything not on the effective list. The default is deny
modestringobserve logs a would-block and lets the call through, warn allows and reports, enforce blocks

The effective list is your allow list plus the hosts of the drivers in use. The Resend driver declares api.resend.com, so you never list provider hosts yourself.

Matching rules:

  • An exact domain does not match its subdomains. google.com does not allow mail.google.com.
  • *.domain matches subdomains only. *.pinecart.dev allows api.pinecart.dev but not pinecart.dev.
  • Prefer domains; IPs behind CDNs change. IPs suit fixed services such as a partner's VPN gateway.
  • A plain entry covers HTTP, HTTPS and WebSocket only. Raw TCP and UDP need an entry with the protocol and port, such as tcp://db.acme.dev:5432 or udp://8.8.8.8:53, and listening needs network.listen. See TCP and UDP sockets.

Inherited and overlay lists

main.org.yaml can set the list for every app. An app can remove hosts (tightening), but adding a host the org does not allow is weakening: it fails with MAIN-CONFIG-0010 unless an exception names the rule, a reason, an owner and an expiry:

main.yaml
exceptions:
  - { rule: net.default_deny, reason: "partner VPN", owner: "@pinecart/platform", expires: 2026-11-30 }

An expired exception fails like no exception. See main.yaml.

Helpers

http.fetch is the base. The helpers below cover the calls apps make most often. Each one goes through the same allow-list check, and the decision names the calling file and line.

CallUse it forReturns
http.fetch(url, init)Any request, when you want to read the response yourselfResponse
http.get(url)A plain GETResponse
http.post(url, body)A POST; an object body is sent as JSONResponse
http.json(url, { body, schema })A request whose answer is JSON. Sends body as JSON when it is given, and checks the answer against schema when you pass one (MAIN-SCHEMA-0001 on a mismatch). Throws MAIN-NET-0008 on a non-2xx answer or a body that is not JSONthe parsed value
http.events(url)Server-sent events, one event per iterationan async iterator of { event?, data }
http.websocket(url, protocols?)A WebSocket. The host is checked before the connection opensWebSocket
http.assert(url)A database URL or git remote that the SDK does not carry for you. The host is checked before you connectthe decision
lib/partner.ts
import { http } from "mainsdk";

// allowed: api.partner.com is in network.allow
export async function quote(sku: string) {
  return http.json<{ price: number }>("https://api.partner.com/v1/quotes", { body: { sku } });
}

export async function watchPrices() {
  for await (const event of http.events("https://api.partner.com/v1/prices/stream")) {
    console.log(event.data);
  }
}

export async function openFeed() {
  return http.websocket("wss://feed.partner.com/live");
}

Use http.assert before a driver or library opens a connection the SDK cannot wrap, such as a database client. It throws MAIN-NET-0002 for a host that is not allowed, and returns the decision when it is:

lib/db.ts
import { http } from "mainsdk";

const url = process.env.DATABASE_URL ?? "";
await http.assert(url);   // throws MAIN-NET-0002 if the database host is not allowed

Server-side programs such as git or curl run through system.run (see Server facts and programs). Every URL in their arguments is checked against this list too.

Reliable calls

Every outbound call is bounded and retried when it is safe, and a host that keeps failing is cut off. You write http.get() and get the same behaviour as every driver, with no timeout or retry loop of your own.

main.yaml
network:
  http:
    timeout: 10s           # one attempt, until the body is read; "off" for none
    connect: 3s            # one attempt, until the response headers arrive
    retries: { attempts: 3, backoff: 200ms, max: 5s, deadline: 30s }   # or retries: off
    breaker: { failures: 50%, window: 30s, min: 10, cooldown: 15s }   # or breaker: off
    hosts:                 # an exact host or "*.domain", as in network.allow
      api.partner.com: { timeout: 3s, retries: { attempts: 5 } }

These are the defaults, except hosts. Settings resolve per field, most specific first: the call, then the host's entry in hosts, then the driver's defaults for that host, then network.http, then the built-in defaults.

Options on each call

OptionWhat it does
timeoutTotal time for one attempt, from the request until the body is read. "off" disables it
connectTime for one attempt until the response headers arrive
retriesfalse, a number of attempts, or { attempts, backoff, max, deadline } for this call
idempotencyKeySent as Idempotency-Key on every attempt. It makes a POST retryable
lib/partner.ts
import { http } from "mainsdk";

// a slow report: give it more time, and do not retry it
export function report(id: string) {
  return http.get(`https://api.partner.com/v1/reports/${id}`, { timeout: "30s", retries: false });
}

// a charge: the key makes the POST safe to retry
export function charge(orderId: string, body: { amount: number }) {
  return http.post("https://api.partner.com/v1/charges", body, { idempotencyKey: orderId });
}

Use one key per logical call and reuse it on every retry. A new key for a retry defeats the point.

What is retried

  • Only GET, HEAD, PUT, DELETE and OPTIONS are retried, and a POST that carries an idempotencyKey. A request whose body is a stream is never retried.
  • A retry happens after a network error, a timed-out attempt, or a 408, 429, 502, 503 or 504 answer. Any other answer is returned as it is.
  • Waits use full jitter: a random wait up to backoff doubled each attempt, capped at max. A Retry-After header on a 429 or 503 replaces that wait, even above max.
  • A call stops after attempts tries or when the next wait would pass deadline. It then returns the last answer, or fails with MAIN-NET-0009 (timed out) or MAIN-NET-0011 (network error).
  • A call blocked by the allowlist (MAIN-NET-0002) is never retried and never counts against the breaker.

The circuit breaker

Each host has its own breaker in each app instance. When the share of failed attempts in the last window reaches failures, with at least min attempts in it, the breaker opens. A failure is a network error, a timeout, or a 500, 502, 503 or 504; a 429 or other 4xx is not.

While the breaker is open, a call to that host fails at once with MAIN-NET-0010 and sends nothing. After cooldown one probe call goes through; if it succeeds the breaker closes, and if it fails the breaker opens again. Calls to other hosts are unaffected. A breaker's state is per instance and starts closed on every start.

Every attempt and every breaker change is written to the decision log, with the attempt number, its wait and its outcome. Bodies, Authorization headers and Idempotency-Key values are never logged.

Find what your code calls

bash
npx mainsdk discover

discover reads your source and main.yaml and lists every host the code connects to, with the file and line, the kind of connection and whether main.yaml declares it. It also lists the server folders, facts and programs the app uses, which are covered by Declare what your app uses. Each undeclared host is marked MISSING, and the output ends with the network.allow lines to add.

text
network
  declared http         https://api.partner.com  lib/partner.ts:5
  MISSING  websocket    wss://feed.partner.com  lib/partner.ts:15

Three more ways to use it:

  • npx mainsdk discover --json prints the full manifest for scripts and agents.
  • npx mainsdk discover --out build/manifest.json writes the manifest to a file at build time, so a review or a platform rule can read what the app uses without running it.
  • npx mainsdk network export --to k8s --discovered adds the hosts the code uses that network.allow doesn't list yet.

mainsdk check fails with MAIN-NET-0007 for any host the code uses that network.allow doesn't declare. A URL that is only known when the app runs, such as one read from a variable, is listed as dynamic: discovery can't read its host, so the runtime check is what guards it.

Examples

Call a partner API from a route handler

app/api/quotes/route.ts
import { auth, http } from "mainsdk";

export async function POST(req: Request) {
  await auth.require(req);
  const body = (await req.json()) as { sku: string; qty: number };

  const res = await http.fetch("https://api.partner.com/v1/quotes", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ sku: body.sku, qty: body.qty }),
  });
  return Response.json(await res.json(), { status: res.status });
}

http.fetch(url, init) is the client every driver and app call uses, so you write it like fetch.

Roll a new list out safely

Adopting network on an existing app: start in observe, read what would be blocked, add what is legitimate, then enforce.

main.env.prod.yaml
network:
  mode: observe     # calls succeed; would-block decisions are logged

Every block or would-block is written to the decision log with the destination, the rule and the calling file and line, and shows in mainsdk info:

bash
npx mainsdk info --json

Once the log is clean, switch the overlay to warn, then enforce.

Internal services from an Express worker

Private ranges are reachable only when you declare them. A DNS answer that points an allowed name at a private address you did not declare is refused as rebinding.

main.yaml
network:
  allow:
    - "*.pinecart.dev"
    - 10.20.0.0/16        # the internal billing service
  default: deny
worker/billing.ts
import { createServer } from "node:http";
import { http } from "mainsdk";

createServer(async (req, res) => {
  const id = req.url?.split("/")[2] ?? "";   // /invoices/:id/sync
  const upstream = await http.fetch(`http://10.20.4.12/invoices/${id}`);   // inside 10.20.0.0/16
  res.writeHead(upstream.status, { "content-type": "application/json" });
  res.end(JSON.stringify(await upstream.json()));
}).listen(3000);

A second wall at the platform

Export the same list as platform egress rules, so the network blocks undeclared hosts even if code finds a way around the SDK.

bash
npx mainsdk network export --to k8s

Targets are k8s (a NetworkPolicy) and aws-sg (security group egress rules). Exports for Vercel and Cloudflare are planned. Platform rules match IPs, so names are resolved when you export. For Kubernetes the policy holds your CIDR egress rules, such as 10.20.0.0/16 and 8.8.8.8/32. --discovered adds hosts the code uses that network.allow does not list yet.

Reference: the network layer

The Network instance behind http has calls of its own. They are for adapters, drivers, tests and tooling. Most apps use only the http helpers above.

CallWhat it does
network.fetch(input, init?)Fetch through the allow list with timeouts, retries and the host's circuit breaker. http.fetch calls it.
network.assert(target)Check a destination the SDK doesn't carry itself, such as a database URL, a WebSocket or a git remote. It throws MAIN-NET-0002 when enforce mode blocks it. http.assert calls it.
network.decide(url, reason?, caller?)Decide one URL: the host must be declared, and DNS answers must not point at an undeclared private range. Returns the decision, with the calling file and line.
network.matchHost(host)The allow rule that covers a host name, or null.
network.matchIp(ip)The CIDR rule that covers an address, or null.
network.settingsFor(host, call?)The timeouts, retry and breaker settings a call to this host resolves to, with the call's own options on top. For tests and mainsdk info.
network.reload(opts)Replace the allow list, mode and default while the app runs. Driver hosts stay allowed. opts takes allow, default and mode.
network.breakerStates()The circuit breaker state of each host, as { host, state }. health() reports them as remote dependencies.
network.breakerSlots()How many numbers the breakers hold in all. It grows with hosts, never with traffic.
network.trim()Drop the older half of the decision log. A pressure hook calls it under memory pressure.
network.modeThe mode in force: observe, warn or enforce. network.reload() changes it.
network.rulesThe parsed allow rules, in the order network.allow lists them.
network.decisionsThe newest 1,000 decisions, each with the host, allow or deny, the rule that matched and the calling file and line.
network.entriesDecisions, attempts and breaker changes in order, the newest 1,000.

Customer-chosen URLs

A URL a customer typed, such as a webhook endpoint, must never reach a private address, and no network.allow entry can change that. These two calls take no allow-list entry.

CallWhat it does
network.decidePublic(url, caller?)Check that a URL is https, carries no credentials, and that its host and every address it resolves to now are public. Throws MAIN-HOOK-0004 without connecting.
network.fetchPublic(input, init?)Send one request to such a URL. The address is checked again at every connect, no redirect is followed and nothing is retried. The answer comes back whatever its status.

Helpers

These functions are exported for tooling and tests.

FunctionWhat it does
parseRule(text)Parse one allow entry: an IP, a CIDR range, an exact host or *.domain. Throws MAIN-NET-0005 for anything else.
isPrivateIp(ip)true for loopback, private, carrier-grade NAT and link-local addresses.
createHttp(net)Build the http client on one network layer, given a function that returns it. Tests use it to run http on a separate layer.
exportK8s(allow, opts)Write the allow list as a Kubernetes NetworkPolicy. Names are resolved to /32 or /128 addresses first.
exportAwsSg(allow, opts)Write the allow list as AWS security group rules (IpPermissions JSON).

Both exports take { name?, resolve?, listen? }: a name for the policy, a function that resolves a host to addresses, and the network.listen entries from main.yaml. They are what npx mainsdk network export runs.

What you get

  • Every outbound call goes through the SDK's network layer and is checked against the list in main.yaml, plus the hosts your drivers declare.
  • A poisoned dependency that tries to send your secrets to a new domain fails in CI and is stopped in production.
  • Reach for http.fetch whenever your own code calls an outside API.
  • It runs inside your app and is free on every plan.

Errors and checks

CodeWhenFix
MAIN-NET-0001mainsdk check found a raw socket, another HTTP client (for example node:net, axios), a direct fetch, new WebSocket or EventSource, or git, curl or wget run as a child process outside the SDKUse an http helper, or move the call into a driver; run programs with system.run
MAIN-NET-0002A call went to a host that is not on the effective listAdd the host to network.allow if it is legitimate; otherwise find the code that called it
MAIN-NET-0003An allowed host redirected to an undeclared hostDeclare the redirect target, or call the final URL directly
MAIN-NET-0004A Main 27 network rule, for example a third-party API that offers only TLS 1.1Use an endpoint that offers TLS 1.2 or later, or record an exception with an owner and an expiry
MAIN-NET-0005An entry in network.allow is not an IP, a CIDR range, an exact host or a *.domainFix the entry; the error names it
MAIN-NET-0006A request was redirected more times than the SDK follows, or a redirect: "error" request met a redirectCall the final URL directly, or fix the redirect loop
MAIN-NET-0007mainsdk check found a host the code uses that network.allow doesn't listAdd the host, or remove the call; npx mainsdk discover prints the lines
MAIN-NET-0008http.json or http.events got a non-2xx answer (429 when the provider rate-limits, otherwise 502) or a body that is not JSON. The body is never echoedCheck the URL, the credentials and the service status, or read the response with http.fetch
MAIN-NET-0009An outbound call timed out: the connect or total timeout of its last attempt, or its deadline, passed (504; the error names which)Raise the timeout for that host or call, or check the provider's latency
MAIN-NET-0010The circuit breaker for the host is open, or half-open with its probe in flight. No request was sent (503, retryable, retryAfter is the rest of the cooldown)Wait for retryAfter, and check the provider's status
MAIN-NET-0011An outbound call failed with a network error (refused, reset, DNS failure) on its last attempt (502; states the number of attempts)Check the host, DNS and the provider's status
MAIN-NET-0012A value under network.http is invalid: a bad duration, attempts outside 1 to 10, failures outside 1 to 100 %, min below 1, or an unknown key. It fails at load and names the keyFix the value the error names

The decision log names the destination, the rule and the calling file and line for every block. The check codes for this interface are listed above.

Swapping drivers

network has one driver, built in. Other interfaces' drivers feed it: each driver declares the hosts it calls, and those join the effective list. When you swap email from Resend to SES, api.resend.com leaves the list and the SES host joins it, with no change to network.allow. network.destinations() shows the result.

Driver calls go through the same layer. A driver that reaches a host it did not declare is blocked with MAIN-NET-0002 like any other call. A kit for writing your own drivers (defineDriver, spec cohesion) is planned.

Testing

Route tests run on the in-memory fakes, so they make no real network calls. Assert the guarantee itself with a test named by its criterion:

lib/rates.test.ts
import assert from "node:assert/strict";
import { it } from "node:test";
import { createTestApp } from "@mainsdk/testing";
import { http } from "mainsdk";

it("network AC1 blocks an undeclared host", async () => {
  const app = createTestApp({
    yaml: "schema: 1\nproject: rates\nnetwork:\n  allow: [api.partner.com]\n",
    respond: () => Response.json({ price: 5 }),   // what an allowed host answers
  });
  await assert.rejects(http.fetch("https://evil.example"), { code: "MAIN-NET-0002" });
  assert.deepEqual(await http.json("https://api.partner.com/v1/rates"), { price: 5 });
  assert.equal(app.sent.length, 1);   // only the allowed call was sent
});

respond answers every call the list lets through; without it the fake answers 204. dns maps names to fake addresses for the rebinding check. See the Testing kit.

Security and privacy

  • Deny by default. Undeclared hosts are blocked and reported.
  • The decision runs in process, with no network round trip.
  • DNS answers are checked against the list, so a name cannot be rebound to a private range you did not declare. Redirects to undeclared hosts are refused.
  • Raw sockets and other HTTP clients fail the check at build time.
  • Free on every plan: an app protecting itself is never a paid action.

What Cloud adds

Your allowlist and its enforcement are free in your app. On Fleet, a security team enforces egress allowlists, approved providers and approved models across every app in the estate. Fleet can tighten your list, never loosen it. See free in-app and Cloud.

FAQ

Do I list Stripe, Resend and my database host? No. Drivers declare their own hosts. You list only your own destinations.

Does *.pinecart.dev allow pinecart.dev? No. Wildcards match subdomains only. List pinecart.dev separately if you call it.

Does this cover inbound traffic or CORS? No. network is outbound only. Secure headers, CSRF and CORS are inbound protection; rate limits are in rate limits and quotas.

Can I try it without breaking production? Yes. Set mode: observe and read the would-block decisions first.

List the destinations

network.destinations() returns every destination the app may reach: your allow list plus the hosts your drivers declare. It is what mainsdk info prints.

lib/egress.ts
import { network } from "mainsdk";

const hosts = network.destinations();   // ["api.partner.com", "api.resend.com", ...]

Next

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet