Hosts and Ports
An allow_http block is one grant in two levels. The label says which
destination may be reached; the body says which requests may be sent there.
allow_http "api.example.com" { allow_requests ["GET /v1/**"]}Keeping them together is what makes a diff readable: delete the host and its rules go with it.
One block, several hosts
Section titled “One block, several hosts”The label may be a list, and then every host in it shares the one body:
allow_http ["a.example.com", "b.example.com:9123"] { allow_requests ["GET /health", "* /v1/**"]}This is exactly the same as writing a separate block for each host with the
same rules — it resolves to one grant per host — but the rules live in one
place, so they cannot drift out of step between the hosts. Reach for it when
several destinations are governed by one rule set; keep the single-string form
for a host with rules of its own. The list is an option, never a requirement:
a one-host grant stays allow_http "api.example.com" { … }, no brackets.
What a label matches
Section titled “What a label matches”| Written | Matches |
|---|---|
"api.example.com" | that host, ports 80 and 443 only |
"api.example.com:8443" | that host on 8443 only |
"api.example.com:*" | that host on every port |
"*.example.com" | one subdomain label — api.example.com, not a.b.example.com and not the apex |
"**.example.com" | any subdomain depth — api.example.com and a.b.example.com, still not the apex |
"registry-*.npmjs.org" | a generated-name family, in one label |
"10.0.0.0/8:*" | a network, matched against literal addresses only |
"[2001:db8::1]:443" | an IPv6 address — bracketed, so the port is unambiguous |
"*" | any host, still on 80 and 443 |
A bare host never means every port. Granting https should not silently grant ssh on the same machine.
Wildcards in a hostname
Section titled “Wildcards in a hostname”A label can carry wildcards — the same * / ** the filesystem globs use:
*matches any run of characters within one label and never crosses a dot.registry-*.npmjs.orgmatchesregistry-1.npmjs.orgbut notregistry.cdn.npmjs.org. A whole-label*fixes the depth:console.*.aws.comisconsole.<one label>.aws.com, and*.*.aws.comis exactly two subdomain labels.**matches one or more whole labels, so any-depth reach is written**.example.com— every subdomain, however deep, but never the apex.
The rightmost label must be literal. A wildcard there would open a whole
top-level domain, so foo.* and *.* are refused, and “any host at all” keeps
its one spelling, the bare *. (*.com is accepted — without a public-suffix
list the resolver cannot tell a public suffix from a private one like
.internal — but it is rarely what you mean.)
This grammar lives in the destination, not the verb, so every net rule reads it
the same: allow_http, allow_tcp, allow_udp, and the deny_* / mute_hosts
forms alike.
A host can be granted twice
Section titled “A host can be granted twice”Two labels for the same host on different ports are two separate grants, and neither rules on the other’s requests:
allow_http "api.example.com:443" { allow_requests ["GET /safe"] }allow_http "api.example.com:8443" { allow_requests ["* /**"] }A request to :443 is decided by the first block only.
Empty blocks are an error
Section titled “Empty blocks are an error”allow_http "api.example.com" { } # ← errorNot “allow everything”. To grant a host completely, say so:
allow_http "api.example.com" { allow_requests ["* /**"] }Denying
Section titled “Denying”deny_hosts ["ads.example.com", "*.ads.example:*"]Beats every allow, from any fragment, and covers every protocol. There is no
deny_host block form — a deny needs no body.
Muting
Section titled “Muting”mute_hosts ["telemetry.example.com"]Keeps an expected-blocked host’s denials out of the logs without deciding
anything — the host stays blocked by the absence of a grant, and unlike
deny_hosts a mute is overridable by a more local fragment. See Muting a
host for when to use it over
deny_hosts.
Granting everything
Section titled “Granting everything”allow_http "*" { allow_requests ["* /**"] }Still routed through the proxy and still logged. Note it is every host on 80
and 443 — every port anywhere is allow_tcp ["*:*"], in the verb whose grants
are documented as weaker.
Literal IPs and DNS
Section titled “Literal IPs and DNS”An entry written as a name matches a name; an entry written as an address
matches an address. They never cross, because a name’s resolution is checked
separately: after DNS, the proxy vets where the name actually landed, so a
name resolving into a denied range is refused. That closes DNS rebinding
without you writing anything. A wildcard name is still a name: 127.*.0.1
never matches the literal address 127.0.0.1, however its dots line up — name
a network with a CIDR (127.0.0.0/8) instead.
Reaching a service on the host: host.internal
Section titled “Reaching a service on the host: host.internal”host.internal is a reserved name for a service the developer is running on the
host machine’s own loopback — a local Postgres, a dev gateway. Grant it like any
other target:
net { allow_tcp ["host.internal:5432"] # raw splice to the host's Postgres allow_http "host.internal" { # or inspected, with per-request rules allow_requests ["* /**"] }}It means the same thing on macOS and Linux, so one policy — and one client
connection string — works on both. The sandbox cannot dial the host’s
127.0.0.1 itself (on Linux it has its own isolated loopback; on macOS it
shares the host’s), so the proxy dials 127.0.0.1 on the sandbox’s behalf,
after the grant is checked. A deny_hosts ["127.0.0.1:*"] still wins.
Use host.internal for anything on the host, and keep 127.0.0.1 for the
sandbox’s own processes — the two differ by platform, so see
macOS and Linux.
.internal is reserved for private use, so the name cannot collide with a real
one.