An operation allowlist fixes which documents run — what abuse still arrives through variables?
answer
- Half the request was never registered
- The document is fixed, the bindings are not
- Magnitude, ownership, frequency
- Membership carries no score
- Score once at registration, not per request
basics
~20 sAn allowlist constrains a request's shape, never its values. Every argument a registered document exposes as a variable stays caller-controlled — page sizes, record identifiers, filter strings — so range checks, authorization and rate limits still run on every request.
solid answer
~50 sRegistering a document freezes its selection set; it leaves every variable the document declares under the caller's control. In a hospital appointment graph, an approved `ClinicSchedule` document taking `$clinicId` and `$first` is still a legitimate document when it arrives with `first: 25000` against a clinic the caller has no relationship with. Three controls therefore survive the allowlist untouched: a **server-side maximum** on list-slice arguments, **authorization on every resolved edge** rather than on the entry field, and **per-caller rate limiting**, because a cheap registered document run three thousand times a minute is a load event. Set membership is not a cost budget either: a 19-level-deep document that was registered stays allowed at 19 levels until somebody removes it. The genuine win is that a finite, known set can be scored once at registration, against the worst variable values you permit.
code
graphql · 10 linesquery ClinicSchedule($clinicId: ID!, $first: Int!, $status: AppointmentStatus) {
clinic(id: $clinicId) {
name
appointments(first: $first, status: $status) {
id
scheduledFor
patient { id displayName }
}
}
}go deeper
Hold on to the split: the document text is fixed by registration, the variable values are not. A request that arrives with an approved document can still carry values nobody reviewed.
Name the surviving controls concretely — a server-side maximum on list-slice arguments, authorization during execution, per-caller rate limits — and explain why the declared variable type is not a range check.
Work a real registered document and show three hostile bindings for it. Then make the operational argument: a finite known set lets cost scoring move to registration time, provided it is scored against the worst values you allow.
Own the budget question. After enforcement the attacker's remaining freedoms are values and frequency, so argue for moving defensive investment there and for a registration gate somebody is genuinely reading.
## Shape versus values An executable document has two halves. The **shape** is fixed text: which fields are selected, how deep the selection goes, which fragments are spread, which arguments exist. The **values** arrive per request, as `variables`. An operation allowlist governs the first half completely and the second half not at all. Candidates who have only read about allowlists routinely miss this, because the mechanism is described as "only approved queries run" and *approved* sounds like *safe*. Take a registered document from a hospital appointment graph: ```graphql query ClinicSchedule($clinicId: ID!, $first: Int!, $status: AppointmentStatus) { clinic(id: $clinicId) { name appointments(first: $first, status: $status) { id scheduledFor patient { id displayName } } } } ``` This document was reviewed, hashed and registered. Everything below is a request that the allowlist admits without hesitation. ## The four things that still arrive **A hostile magnitude.** `first: 25000` is a valid `Int`. The declared type constrains the kind of value and the 32-bit range the specification gives `Int`; it does not express a sane maximum. The single most common gap after an allowlist rollout is an uncapped list-slice argument, because the document "looks small" — one field, three subfields — while the work it triggers is set at request time by the caller. A server-side clamp, applied in the resolver or the data layer rather than trusted from the client, is the fix. **A record the caller may not see.** `clinicId: "clinic_4471"` is an identifier for a clinic in another region. Nothing about registering the document decided who may read that clinic's schedule. Authorization has to run during execution, on the edges actually traversed — the clinic, then each appointment, then each patient — not once on the entry field. A registered document is a very convenient vehicle for enumeration precisely because it is guaranteed to be accepted: an attacker with a valid session iterates identifiers and reads what comes back. **A value that reaches a backend.** A free-text `$search` or a `$sortBy` variable that is interpolated into a downstream query is untrusted input that happens to have passed through an approved document. The allowlist did not sanitise it, parameterise it, or bound its length. **Volume.** The document is cheap; ten thousand executions of it a minute are not. Per-caller rate limits and concurrency budgets are unaffected by membership, and after an allowlist rollout they often become the *only* remaining brake, because everything about the document is now fixed and the attacker's only free variables are values and frequency. ## Membership is not a budget The second half of the misconception: teams sometimes drop depth and cost controls after enforcing an allowlist, on the reasoning that every document was reviewed. A 19-level-deep document that someone registered eleven months ago during a spike is allowed at 19 levels for as long as it stays in the set, and it will not be re-examined by anything on the request path. Membership is a boolean; it carries no score, no expiry and no memory of why it was approved. The corollary is the real prize, and it is what a senior answer should reach for. Because the set is **finite and known before traffic arrives**, the expensive analysis moves off the hot path. Each document can be scored once, at registration, against the worst variable values the server will accept — and that qualifier matters, because a score computed with `first: 25` for a document that permits `first: 25000` is a fiction. A document that scores badly is refused at the registration gate, where the failure is a build error for one team rather than a runtime rejection for real users. This is the strongest operational argument for allowlists and it is the one candidates least often make. ## Designing the residual controls A good answer names where each surviving control lives: - **Range and shape of values** — coerced by the schema for type, clamped by the server for magnitude, with the clamp expressed once in the data layer rather than repeated per resolver. - **Ownership** — checked on every resolved edge during execution, because one object is reachable through several traversals and only the edge knows the caller's relationship to it. - **Frequency** — per caller and per credential, on the operation name or document identifier, which an allowlist actually makes easier: with a fixed set you have a stable, low-cardinality key to limit and to chart. - **Registration-time review** — the one control the allowlist adds, and the one that only works if somebody is genuinely reading what gets registered. ## How to answer it Open with the distinction — shape fixed, values free — then give one concrete registered document and three concrete hostile variable bindings for it. Close on the asymmetry: the allowlist removed the attacker's ability to *write* the request, so everything they can still do lives in the values and in the rate, and that is exactly where the remaining budget of your defensive effort should go.
- Does an allowlist at least make cost analysis easier?Substantially. The set is finite and known before traffic, so each document can be scored once at registration and a too-expensive one refused there, where it is a build failure for one team rather than a runtime error for users. The caveat is that the score must be computed against the worst variable values the server accepts, not against the values the client happens to send.
- Which variable does this review most often miss?The pagination slice. Teams cap depth and field counts and leave `first` or `last` unbounded, because the document is visibly small. One field with a five-figure slice outweighs a deep document, and it arrives inside a request that every control upstream has already approved.
- If every document is registered, can per-request variable handling be skipped?No. Variable coercion against the declared types is part of execution and happens regardless — and it only checks the kind of value, never a sane range or the caller's relationship to a record. Skipping it is not an option the allowlist offers.
- How does an allowlist change rate limiting?It makes it better, not unnecessary. A fixed set gives a stable, low-cardinality key — the document identifier — so budgets and dashboards can be per document per caller instead of lumping one endpoint together. The limit itself still has to exist; after enforcement, frequency and values are the attacker's only remaining freedoms.
Fixing the document is like printing the questions on a form. The applicant still writes the answers, and nothing about a pre-printed form makes what they write true or harmless.
saying these in an interview costs you the question
- Treats a registered document as inherently safe
- Drops pagination caps after enabling an allowlist
- Assumes the allowlist validates variable values
- Reads set membership as a cost budget
- Trusts the client to send sane magnitudes
- Skips authorization because the document was approved