skip to content

What is a GraphQL operation allowlist, and what abuse does registering documents close?

level: juniorimportance: should knowfreq 40%

answer

  1. The endpoint stops taking dictation
  2. A fixed set, decided before traffic
  3. Sent by identifier, not by text
  4. The server runs its own stored copy
  5. Bounds authorship, not values

basics

~20 s

An operation allowlist registers the exact executable documents clients are allowed to send. The server accepts a request only when its document is one of those, usually referenced by an identifier, and rejects every other document before parsing it.

solid answer

~50 s

An operation allowlist inverts the default posture of a GraphQL endpoint. Normally the server executes any syntactically valid, schema-valid executable document a caller writes; with an allowlist, the set of documents that may run is fixed before traffic arrives, and a request carrying anything else is refused. Clients reference a registered document by an opaque identifier — typically a hash of the document text — and send that identifier plus `variables` instead of the query text. Nothing in the GraphQL specification defines this; it is a deployment convention, and the request parameter that carries the identifier differs between implementations. What it closes is the whole class of abuse that depends on the attacker *authoring* a document: unbounded nesting, aliased amplification, unusual field combinations, introspection crawling. What it does not do is bound anything that happens inside a document you did register.

code

graphql · 10 lines
graphql
query ClinicSchedule($clinicId: ID!, $first: Int!) {
  clinic(id: $clinicId) {
    name
    appointments(first: $first) {
      id
      scheduledFor
      status
    }
  }
}

go deeper

for a junior

Recall the one-line definition: only documents registered in advance may run, and clients reference them by an identifier rather than sending query text. Know that it is a deployment convention, not something the GraphQL specification defines.

for a middle

Be able to walk the request path — identifier arrives, server looks it up, executes its own stored copy, refuses a miss — and to state precisely what it closes: attacker-authored documents, and nothing else.

for a senior

Show that you have operated one. Name the log-only rollout, the operator escape hatch for incident queries, and the fact that the gate is fail-closed, so a registration gap is an outage rather than a warning.

for a principal

Own the framing that an allowlist converts an open query language into a fixed API surface, and that this is a posture decision with a release-coupling cost, not a feature you switch on because it is available.

## The default posture, and why it is unusual A GraphQL endpoint publishes a schema and then accepts documents written against it. That is the point of the style: one URL, and the caller decides the shape of the answer. It also means the endpoint is, by default, a small query language exposed to whoever can reach it. Every other control discussed on a GraphQL security review — depth caps, cost budgets, per-field authorization — exists because the caller writes the document. An **operation allowlist** removes that premise. The server keeps a set of executable documents that were registered ahead of time, and it will run nothing else. A request no longer carries a query; it carries an identifier for one of the registered documents, plus the usual `variables`. On a hit, the server executes **its own stored copy** of the document — never the text the client sent, if it sent any. On a miss, the request is refused. This is a convention, not a specified feature. The GraphQL specification describes how a document is parsed, validated and executed; it says nothing about registering documents in advance. The GraphQL over HTTP work describes how an operation travels over HTTP, and the parameter used to carry a document identifier is not uniform across implementations — some put a top-level identifier field in the JSON body, some carry it under `extensions`. When you discuss this in an interview, say plainly that it is a widespread convention with several spellings rather than attributing it to the specification. ## What it actually closes Think about a hospital appointment graph: clinics, appointments, patients, clinicians, notes. The published schema has cycles in it — an appointment has a clinic, a clinic has appointments — so a caller can legally write a document nineteen levels deep, or repeat the same expensive field forty times under different response keys, or walk the schema through introspection to find a field that was added last week and not yet reviewed. All of those depend on the attacker writing the document. With an enforced allowlist, none of them arrive, because none of them are in the set. The pinned mobile build ships with roughly forty documents; the back-office web app ships with maybe two hundred; nothing else exists as far as the endpoint is concerned. The reduction is not incremental — it converts an open query language into a fixed, enumerable API surface, one whose entire attack surface you can print out and review. That is also the honest limit of the claim. The allowlist bounds **who authors the document**. It does not bound the values that flow into a registered document, it does not decide who may run one, and it does not make a registered document cheap. A document that was expensive on the day it was registered stays expensive and stays allowed. ## The lookup, and why the client's text is not trusted The mechanic worth being precise about: the identifier is a key into the server's store, and the value in that store is the document that executes. If a client sends both an identifier and a document text, an enforced allowlist ignores the text, or refuses the request outright. Any design where the client's text is what runs on a hit has given the client authorship back, which is the thing the control existed to take away. Because the identifier is normally a hash of the text, the identifier and the document cannot drift apart silently: the same text always yields the same key, and a changed document is a different key that is simply not registered yet. That property is what makes the store safe to populate from a release pipeline rather than by hand. ## What it costs you Three things break the day enforcement goes on, and a candidate who has run one will name them without prompting. First, **ad-hoc exploration stops**. A hand-written request, a browser explorer, a debugging one-liner — each is by definition an unregistered document. Teams keep arbitrary documents enabled in non-production, and behind an operator credential in production, precisely so that an incident is survivable. Second, **the release train becomes a dependency**. A client can only send documents that were registered before it shipped, so the registration step has to land ahead of the client release, and it has to keep working for as long as that client is in the field. Third, **the allowlist is fail-closed by design**. That is the property you wanted; it is also the property that turns a registration mistake into an outage rather than a warning. It is why the standard rollout is a log-only period first, with enforcement switched on only once the miss log is empty of legitimate callers. ## How to answer it Say what it is in one sentence — only pre-registered documents run, referenced by identifier — then draw the boundary yourself: it removes attacker-authored documents, and it removes nothing else. Naming a convention as a convention, and naming the limit before the interviewer probes for it, is what separates a memorised definition from an operated control.

  • If the client only sends an identifier, how does the server know what to execute?
    It looks the identifier up in its store of registered documents and executes the stored text. The client's own copy, if it sends one, is not what runs. Two consequences follow: the store has to be populated before the release that uses it, and the identifier should be derived from the document — a hash — so an identifier and its document cannot drift apart.
  • Does an allowlist replace authorization?
    No. Every request still arrives with a caller and with variables of the caller's choosing. The allowlist decides which questions may be asked; it says nothing about who may ask them or about which records. Field-level authorization and per-caller rate limits are unchanged by it.
  • What breaks in day-to-day work once enforcement is on?
    Anything that sends a document written by hand: an explorer, a curl during an incident, a quick script. The usual accommodation is arbitrary documents in non-production, plus an audited operator path in production, so a fail-closed gate does not get switched off permanently under pressure.

It is the difference between a kitchen that will cook anything you can describe and one with a printed menu: the menu is short, fixed in advance, and "something not on it" is simply refused.

saying these in an interview costs you the question

  • Says an allowlist by itself makes every request cheap
  • Thinks the client's document text executes on a hit
  • Calls operation allowlisting a GraphQL specification feature
  • Believes it removes the need for authorization checks
  • Confuses it with turning introspection off
  • Assumes enforcement can be switched on with no rollout

context