Entries
Each entry inallowlist or blocked is one of:
- A hostname, such as
api.example.com. - A
*.wildcard hostname.*.example.commatchesapi.example.com. It does not matchexample.comora.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, not1.2.3.5/24).
- At most 128 entries. An empty
allowlistmeans no egress; an emptyblockedlist 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.1or169.254.0.0/16, is rejected. - A private range such as
10.0.0.0/8is 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.
/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.blockedandrulesare 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.commatches exactly that host.*.example.commatches 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 underrulesother 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
matchout to cover every request to the host; such a rule must be last in its list. methodis case-sensitive, so writeGET; give one method or a list.path,headers, andqueryvalues 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
respondrule answers requests and leaves the host reachable. Sail still connects to the host before the rule answers, so arespondrule 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 toblocked. - A
forwardhost must be exact. If Sail cannot reach it, the request fails; nothing is sent to the original host. request.set.headersandrequest.set.queryare 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_alpnmap 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
allowlistandblockedhold at most 128 entries each.rulesnames 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::InvalidArgumentin Rust), naming the part to fix. Nothing is created or changed. - Setting
no_networkon a Sailbox that exposes ports or SSH fails withApiError(SailError::Apiin Rust). - Deleting a saved policy that a Sailbox still uses fails with
EgressPolicyInUseError(SailError::Apiin Rust).