Skip to main content
This page is the complete reference for the egress policy document. Read Egress policy first for what a policy is and how to set one.

Entries

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.

What an entry allows

  • 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.

No egress and no network

  • {"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.

Blocked

blocked uses the same entry forms as allowlist, and an entry only ever removes access. Every entry must be narrower than what covers it. 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.

Rules

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:
  • 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 covers storing and rotating secrets.
  • Request bodies cannot be changed, and responses cannot be changed.

missing_alpn

  • 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).