Request Rules
Inside an allow_http block, allow_requests and deny_requests decide which
requests may be sent.
allow_http "api.example.com" { allow_requests ["GET /v1/**", "POST /v1/orders"] deny_requests ["DELETE /**"]}The rule grammar
Section titled “The rule grammar”METHOD /path/globThe method is an upper-case token or *. The path is host-relative and starts
with /.
| Glob | Matches |
|---|---|
/v1/** | /v1 itself and everything beneath it |
/v1/* | exactly one segment under /v1 — not /v1/a/b |
/assets/*.json | * also works within a segment |
/** | everything |
** must be a whole segment: /v1/a** is an error.
Order of decision
Section titled “Order of decision”- Any matching
deny_requestsrefuses. - A
graphqlblock matching the path claims the request (see GraphQL). - Any matching
allow_requestspermits. - Otherwise refused — nothing granted it.
Blocks covering the same host are read as a union, so two fragments each granting one method grant both.
Paths are normalised first
Section titled “Paths are normalised first”This is the part that matters for security. Before any rule is consulted the request target is:
- split from its query string and fragment
- collapsed for repeated slashes
- percent-decoded once
- stripped of
;-parameters - resolved for
.and..
So all of these are gated as /admin, and a deny_requests ["* /admin"] catches
every one:
/admin /./admin /foo/../admin/%61dmin /admin;x=1 /admin%3BignoredAnything ambiguous is refused, not guessed: an invalid escape, a byte
sequence that is not UTF-8, a control character, or a .. that climbs above
the root.
Two deliberate details:
- A
%2Fis data, not a separator./repos/octo%2Fcatis two segments, so/repos/*matches it and/repos/octo/catdoes not. - Decoding happens once.
/%2561dmingates as/%61dmin, not/admin— decoding twice is how a filter and its origin end up seeing different paths.
What is not matched
Section titled “What is not matched”Deliberately out of scope: query strings, request headers, response bodies, and WebSocket frames. A rule matches the method and the path, nothing else.
Narrowing a broad grant
Section titled “Narrowing a broad grant”Start with what policy draft observed, then generalise by hand:
# observedallow_requests ["GET /v1/orders", "GET /v1/orders/8813", "POST /v1/orders"]
# narrowedallow_requests ["GET /v1/orders/**", "POST /v1/orders"]Replace a path with a glob only where the tool will genuinely vary it between runs.