> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sailresearch.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Egress policy reference

> Every rule Sail applies to an egress policy document, and exactly what each entry allows

This page is the complete reference for the egress policy document. Read
[Egress policy](/sailboxes-egress-policy) first for what a policy is and how
to set one.

<h2 id="entries">
  Entries
</h2>

Each entry in `allowlist` or `blocked` is one of:

* A hostname, such as `api.example.com`.
* A `*.` wildcard hostname. `*.example.com` matches `api.example.com`. It does
  not match `example.com` or `a.b.example.com`.
* An IPv4 address, such as `203.0.113.7`.
* An IPv4 range in CIDR form, such as `203.0.113.0/24`. The range must start
  at its first address (`1.2.3.0/24`, not `1.2.3.5/24`).

Each list follows these rules:

* At most 128 entries. An empty `allowlist` means no egress; an empty
  `blocked` list is the same as leaving it out.
* An entry names a destination without a port. An allowed destination is
  reachable on every port, and an entry with a port is rejected.
* IPv6 entries are rejected, because a Sailbox does not reach the internet
  over IPv6.
* An address no Sailbox could ever reach, such as `127.0.0.1` or
  `169.254.0.0/16`, is rejected.
* A private range such as `10.0.0.0/8` is accepted but allows nothing,
  because a Sailbox cannot reach private addresses.
* Entries are stored lowercased, without a trailing dot or duplicates.

<h2 id="what-an-entry-allows">
  What an entry allows
</h2>

* A hostname entry allows connections that name the host, which HTTPS and
  plain HTTP do. It also allows a connection that names no host, such as SSH
  or a database connection, when one of the Sailbox's most recent name
  lookups resolved that hostname to the destination address. A Sailbox that
  resumes from a sleep, a pause, or a checkpoint starts with no remembered
  lookups, so a program should resolve a name right before connecting.
* An address the Sailbox never resolved, such as one read from a config
  file, is reachable only through an address or range entry.
* Name resolution follows the allowlist. A name no hostname or wildcard entry
  covers does not resolve, so an allowlist of only addresses and ranges
  resolves nothing.
* A TLS connection that hides the server name with Encrypted Client Hello is
  closed under an allowlist. Turn that feature off in the client, or add the
  server's address to the allowlist.

Sail resolves an allowed hostname itself and connects to the result, so an
entry in the Sailbox's `/etc/hosts` cannot redirect the connection. Sail
checks only the name a connection announces, so an allowed server that also
serves other sites, as a shared CDN does, makes those sites reachable too.

Under an allowlist or a blocked entry, a connection the policy does not allow
opens and is then closed by Sail, so a program sees the failure on its first
read or write. Under no network the connection is refused before it opens.

<h2 id="no-network">
  No egress and no network
</h2>

* `{"allowlist": []}` is no egress. The Sailbox opens no outbound
  connections, and every name lookup fails. Inbound connections are
  unaffected: exposed ports and SSH keep working, and the policy may be set
  on a Sailbox that exposes them. `blocked` and `rules` are rejected beside
  it, since nothing is left to block and no request is sent for a rule to
  act on.
* `{"no_network": true}` is no network. The Sailbox has no outbound
  connections, no name lookups, and no exposed ports or SSH. Exposing a port
  or enabling SSH is rejected, and the document cannot be set on a Sailbox
  that already exposes one. No other field may be set beside it; a document
  that tries is rejected, naming the field. Running commands, the shell, and
  mounted volumes still work.
* Setting either on a running Sailbox affects connections opened afterwards.
  Under no network, the connections the Sailbox already has open stall
  without closing and resume if a later policy restores network access.

<h2 id="blocked">
  Blocked
</h2>

`blocked` uses the same [entry forms](#entries) as `allowlist`, and an entry
only ever removes access. Every entry must be narrower than what covers it.

| Document          | Accepted                                                                                                |
| ----------------- | ------------------------------------------------------------------------------------------------------- |
| With an allowlist | A hostname under a `*.` wildcard in the allowlist; an address or range inside a range in the allowlist. |
| No allowlist      | Any address or range.                                                                                   |

Anything else is rejected, naming the entry: a wildcard, an entry that also
appears in the allowlist, a hostname on a document without an allowlist, and
any entry beside an empty allowlist or `no_network`.

A hostname can be blocked only under an allowlist: a blocked name takes
effect on connections that name their host, and only an allowlist makes every
connection to a hostname do so. Without an allowlist, block the address or
range instead.

### What a blocked entry does

* A blocked hostname does not resolve, and a connection that announces it
  is refused. Other hosts under the wildcard connect normally. Sail checks
  the name a connection announces when it opens, so a server the blocked
  name shares with an allowed name or address stays reachable through that
  name or address; block the address to keep it off limits.
* A blocked address or range closes any connection to it, whether the
  Sailbox chose the address itself or an allowed hostname resolved to it.

<h2 id="rules">
  Rules
</h2>

`rules` maps a host to an ordered list of rules for the HTTPS requests the
Sailbox sends to that host. Sail picks the most specific host entry for a
request, then the first rule under it that matches, and applies that rule. If
nothing matches, the request goes out unchanged. Rules act on HTTPS only;
plain HTTP and other traffic pass through untouched.

### Hosts

* Write a bare hostname: no `https://`, port, or path.
* `api.example.com` matches exactly that host. `*.example.com` matches any
  direct subdomain. `*` matches every host not named elsewhere. The most
  specific entry wins.
* Use an exact host whenever a rule adds a credential: a wildcard sends the
  credential to every host it matches.

### Rules and the allowlist

* A rule does not make its host reachable. When the document has an
  `allowlist`, every host under `rules` other than `*`, and every host a
  rule forwards to, must be covered by the allowlist: an exact host by the
  same entry or by a wildcard that matches it, and a wildcard by the same
  wildcard entry.
* No rules host or forward host may be a blocked hostname.
* A document that breaks either rule is rejected, naming the host.

### Matching

* Leave `match` out to cover every request to the host; such a rule must be
  last in its list.
* `method` is case-sensitive, so write `GET`; give one method or a list.
* `path`, `headers`, and `query` values accept a plain string or
  `{"equals": "..."}` for an exact match, `{"prefix": "..."}`, or
  `{"one_of": [...]}`.
* A header or query condition can also assert `"present": false`.
* Request bodies cannot be matched.

### Actions

What a rule does is exactly one of:

| Key       | What it does                                                                                                                             |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `request` | `set`, `add`, or `remove` headers and query parameters, or `set` the path. `${secrets.NAME}` works inside `set.headers` and `set.query`. |
| `forward` | Send the request to another HTTPS `host` (and optional `port`). Combine with `request` to add a credential for that host.                |
| `respond` | Answer the request yourself with `status`, `headers`, and `body`. The request is not sent on. Cannot be combined with the other two.     |

* A `respond` rule answers requests and leaves the host reachable. Sail
  still connects to the host before the rule answers, so a `respond` rule
  cannot stand in for a host that is down or does not exist. To block a host,
  leave it out of the allowlist or add it to `blocked`.
* A `forward` host must be exact. If Sail cannot reach it, the request
  fails; nothing is sent to the original host.
* `request.set.headers` and `request.set.query` are templates: every `$` in
  them must be part of `${secrets.NAME}` or `$$` (a literal dollar sign). In
  every other string `$` is plain text, except that an unescaped `${` is
  rejected because it looks like an unresolved reference.
* Only a saved policy may reference a secret, and the secret must exist
  before the policy is saved. [Credential injection](/sailboxes-credentials)
  covers storing and rotating secrets.
* Request bodies cannot be changed, and responses cannot be changed.

<h3 id="missing-alpn">
  `missing_alpn`
</h3>

* A rule applies only when the client announces its HTTP version during the
  TLS handshake (ALPN), which almost every client does.
* For a client that does not, add a top-level `missing_alpn` map that names
  the exact host and the version to assume:
  `"missing_alpn": {"api.legacy.example": "http/1.1"}`.
* The host must have rules, and `"http/1.1"` is the only value.

## Limits

* `allowlist` and `blocked` hold at most 128 entries each.
* `rules` names at most 100 hosts and holds at most 100 rules in total.
* A document is at most 64 KiB.
* A saved policy's name is at most 128 characters of visible text.

## Errors

* An invalid document fails the call with `InvalidArgumentError`
  (`SailError::InvalidArgument` in Rust), naming the part to fix. Nothing is
  created or changed.
* Setting `no_network` on a Sailbox that exposes ports or SSH fails with
  `ApiError` (`SailError::Api` in Rust).
* Deleting a saved policy that a Sailbox still uses fails with
  `EgressPolicyInUseError` (`SailError::Api` in Rust).
