Skip to content

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.

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

A document’s meaning is not visible in its text, so wardcell parses it rather than pattern-matching:

Aliasesx: createOrder is matched on createOrder, never the alias
Fragmentsexpanded before root fields are collected
Multiple operationsthe one operationName selects; ambiguity is refused
Batchesevery element of a JSON array is gated
Introspectionallowed — it is a query, see below
Subscriptionsrefused

A regex over a GraphQL document is defeated by any of the first three, and by the literal text mutation { inside a string variable.

Three, and only three:

Document is in
POST + application/jsonthe query field, or each element of an array
POST + application/graphqlthe 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.

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.

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.