skip to content

With GraphQL introspection disabled, what can a caller still infer about the schema?

level: middleimportance: must knowfreq 54%

answer

  1. The switch is narrower than it sounds
  2. Only two meta-fields get rejected
  3. Failed documents talk back
  4. One meta-field is never blocked
  5. Your own bundle ships the documents

basics

~20 s

Most of it, given time. Validation errors commonly name types and suggest near-miss field names, __typename still reports concrete type names, and a web client ships the documents it sends. Disabling introspection raises enumeration cost, it does not end it.

solid answer

~50 s

Disabling introspection is unspecified and is normally implemented as a validation rule that rejects documents selecting `__schema` and `__type` — so everything else about validation stays exactly as it was. Three channels remain. First, error text: many servers append a "Did you mean ...?" suggestion to an unknown-field error, which turns a blind guess into an oracle, and unknown-argument and variable-type errors name the argument and the expected input type outright. Second, `__typename` normally keeps working, so an abstract result still reports its concrete type names. Third, the schema is already outside your building: a browser client's bundle contains every document it sends, and a mobile binary can be unpacked the same way. Closing the gap means suppressing suggestions, keeping validation messages generic, and — the real control — allowlisting operations and authorizing fields at execution.

code

json · 8 lines
json
{
  "errors": [
    {
      "message": "Cannot query field \"privilegedNotes\" on type \"Case\". Did you mean \"privilegedNoteCount\"?",
      "locations": [{ "line": 1, "column": 24 }]
    }
  ]
}

go deeper

for a junior

Know the headline: turning introspection off does not make field names secret, because error messages and the client's own shipped documents still describe the schema. Being able to name one leaking channel is enough at this level.

for a middle

Explain the mechanism, not just the outcome — the switch rejects two meta-fields and leaves validation untouched, so unknown-field, unknown-argument and variable-type errors keep naming things. Say plainly which of that is specified and which is implementation convention.

for a senior

Demonstrate the ranking: authorization and an operation allowlist change what a caller can do, suppressing suggestions changes what they can learn, and you should be able to state the developer-experience cost you accept when you turn messages generic.

for a principal

Own the position that schema shape is not a secret you can keep, and set the policy that follows from it — no field whose safety depends on nobody knowing its name, and a documented split between production and pre-production error verbosity.

## Start with what "disabled" actually means The specification defines the introspection system and expects servers to answer it; it defines no way to switch it off. Every implementation that offers the switch does the same unspecified thing: a validation rule that rejects any document selecting `__schema` or `__type` on the query root. That is a narrow change. Parsing is untouched, the rest of validation is untouched, execution is untouched, and every other meta-field is untouched. Knowing that, you can predict what leaks without testing it. ## Channel one: validation error text This is the channel that surprises people, and it is the one an interviewer is usually fishing for. The specification requires that a document naming a field that does not exist on the selected type fail validation. It says nothing about the message. Many implementations, following the one most others were modelled on, produce something like `Cannot query field "privilegedNotes" on type "Case". Did you mean "privilegedNoteCount"?`. That single string confirms the type `Case` exists, confirms `privilegedNotes` does not, and hands over a real field name that was never guessed. **This is a convention of implementations, not a rule of the specification** — which is exactly why it can be turned off without breaking conformance. Suggestions are not the only leak in that text. Unknown-argument errors name the arguments a field does accept. A variable used in the wrong position produces an error naming the expected input type, so `$filter` typed as `String` reveals `CaseFilterInput`. A missing required argument is reported with the argument's name and its non-null type. String-similarity suggestions also appear for type names in fragment conditions and for enum values in some implementations. And every one of these is cheap. A validation failure is a *request error*: the response carries `errors` and no `data` key, and no resolver ever runs — so probing is fast, leaves no business-logic trace, and costs the attacker nothing worth rate-limiting against. Walking a legal case-file graph from three known root fields to a workable map of its `Case`, `Filing` and `PrivilegedNote` types is a scripted afternoon, not research. ## Channel two: `__typename` `__typename` is a meta-field available on every object, interface and union selection, and a server that blocks introspection by rejecting `__schema` and `__type` almost never blocks it — clients depend on it to discriminate abstract results, and normalized client caches use it as part of a cache key. Selecting it on a field whose declared type is an interface or union returns the concrete type name, so the shape of an abstract branch of the schema can be enumerated from live results without touching introspection at all. ## Channel three: everything already outside the server A browser client's JavaScript bundle contains the text of every operation it sends, along with the fragments and variable types they use. A mobile binary can be unpacked for the same strings. Public developer documentation, a schema published to a registry for partners, an error `extensions` entry carrying a schema-aware code, a status page describing a new feature — all of them describe the schema. If your own product ships the documents, the portion of the schema your product uses is public whatever the endpoint answers. ## What actually narrows it Ranked by what each one buys: 1. **Authorize fields during execution.** This is the only control that changes what a caller can *do*. Everything else changes what they can *learn*. 2. **Allowlist operations.** If the server only executes documents registered ahead of time, an unregistered probe is rejected before validation ever reports a field name, which closes the error-text oracle as a side effect. The mechanics of that control belong to its own topic; here it is simply the strongest answer. 3. **Suppress suggestion hints and keep validation messages generic.** Cheap and effective against enumeration, with a real cost: your own developers lose the message that would have told them they typed the field name wrong. Teams often keep suggestions on in non-production deployments and off in production. 4. **Disable introspection.** Worth doing on an endpoint whose callers are all first-party, but as the last item on this list rather than the first. ## The honest summary Disabling introspection converts a one-request schema dump into a scripted enumeration exercise. That is a genuine increase in attacker cost and it is not nothing. It is also not a boundary, because none of these channels leak anything an attacker could not eventually infer, and none of them are what stops an unauthorized operation from executing.

  • Are the "Did you mean ...?" suggestions in a validation error required by the specification?
    No. The specification requires that a document selecting a non-existent field fail validation; the wording of the message is entirely up to the implementation. Suggestions are a widespread convenience convention, which is why suppressing them is a configuration choice rather than a conformance question.
  • Why does probing through validation errors cost an attacker so little?
    Because a validation failure is a request error: the document is rejected before execution, so no resolver runs and no backend work happens. The response carries `errors` and no `data` key, comes back fast, and leaves nothing in business-logic logs. Volume-based defences aimed at expensive operations do not see it.
  • If you suppress field suggestions in production, what do you lose?
    Your own developers' fastest feedback loop. A typo in a field name becomes an opaque rejection instead of a message naming the field they meant. The usual compromise is to keep detailed validation messages in development and pre-production deployments and generic ones in production, accepting that a first-party bug now needs a local reproduction.

Locking the card catalogue does not empty the library. The titles are still on the spines, and a librarian who answers "we have nothing called that, did you mean this?" rebuilds the catalogue for anyone patient enough to keep asking.

saying these in an interview costs you the question

  • Assumes disabling introspection hides the field names
  • Thinks the spec mandates the suggestion text
  • Believes blocking __schema also blocks __typename
  • Forgets the web client bundle carries the documents
  • Calls validation probing expensive or rate-limited
  • Treats generic error messages as a security boundary

context