GraphQL
Every GraphQL request is POST /graphql. The method and path say nothing about
what it does, so wardcell reads the body.
allow_http "gql.example.com" { graphql "/graphql" { allow_mutations ["createOrder", "cancelOrder"] max_request_bytes = 65536 }}The block grants the endpoint. A document naming any other mutation is refused.
The endpoint label is exact
Section titled “The endpoint label is exact”* and ** are rejected in it. A graphql "/graphql/**" would quietly claim
every path beneath it, and "/**" would turn every request to the host into
parse-GraphQL-or-refuse. Two endpoints are two blocks.
What the parser sees through
Section titled “What the parser sees through”A document’s meaning is not visible in its text, so wardcell parses it rather than pattern-matching:
| Aliases | x: createOrder is matched on createOrder, never the alias |
| Fragments | expanded before root fields are collected |
| Multiple operations | the one operationName selects; ambiguity is refused |
| Batches | every element of a JSON array is gated |
| Introspection | allowed — it is a query, see below |
| Subscriptions | refused |
A regex over a GraphQL document is defeated by any of the first three, and by
the literal text mutation { inside a string variable.
Transports
Section titled “Transports”Three, and only three:
| Document is in | |
|---|---|
POST + application/json | the query field, or each element of an array |
POST + application/graphql | the whole body |
GET with ?query= | the query string |
Everything else is refused — a multipart body, any other method, and persisted queries. An APQ request sends a hash and expects the server to supply the document, so there is nothing to inspect; allowing it would let any registered mutation through regardless of the list.
Body bounds
Section titled “Body bounds”max_request_bytes caps what is buffered for inspection. A body above it is
refused, never truncated — a truncated document parses into a different
document from the one that would execute.
Defaults to 64 KiB, capped at 4 MiB. A fragment may lower it, never raise it; where two fragments set it, the smaller wins.
The limit worth knowing
Section titled “The limit worth knowing”A mutation allowlist controls writes, not reads.
query { allUsers { ssn } }That passes an endpoint whose allow_mutations list is empty, because it is
not a mutation. Introspection is the same — __schema is a query.
That is the honest reading of a mutation allowlist. If reads matter for your
endpoint, allow_mutations is not the control you need.