GraphQL introspection: what it exposes and when to turn it off
published
TL;DR
GraphQL introspection is a built-in query that returns your complete schema: types, fields, arguments, enums, and deprecation notes. It powers your IDE autocomplete and tools like GraphiQL. In production it also hands an attacker a full map of your API. Disable it in production — but know that disabling it alone is not security, because field-suggestion errors leak schema names anyway.
What introspection is
Introspection is part of the GraphQL specification. Every compliant server exposes meta-fields — __schema, __type, and __typename — that describe the schema itself. A minimal probe:
{
__schema {
queryType { name }
mutationType { name }
types {
name
fields {
name
args { name }
}
}
}
}
The full introspection query that GraphiQL and Apollo Sandbox send is larger — it pulls every type’s kind, every field’s type and arguments, input objects, enum values, interfaces, unions, and the @deprecated reason strings. One request returns the entire shape of your API (graphql.org — Introspection).
Why it matters in production
Introspection is designed for tooling, and in development it is exactly what you want. In production it changes the attacker’s job from guessing to reading:
| Without introspection | With introspection |
|---|---|
| Attacker must brute-force field and type names | Attacker downloads the full schema in one query |
| Internal/deprecated fields are hidden by obscurity | @deprecated(reason: "use newField") advertises history and intent |
| Mutation names unknown | Every mutation, including admin-ish ones, is listed with arguments |
| Input shapes unknown | Exact required arguments and types are enumerated |
A schema map is reconnaissance: it reveals mutations you forgot to lock down, fields named internalNotes or ssn, and deprecation comments that narrate your refactors. None of that is a vulnerability by itself, but it removes every guessing step before one.
What to do
Disable introspection in production. In Apollo Server it is one flag:
new ApolloServer({
schema,
introspection: process.env.NODE_ENV !== 'production',
});
In a graphql-js setup, enforce it as a validation rule so it applies to every execution path:
import { NoSchemaIntrospectionCustomRule } from 'graphql';
const validationRules =
process.env.NODE_ENV === 'production' ? [NoSchemaIntrospectionCustomRule] : [];
Then layer the controls that actually constrain abuse — disabling introspection only hides the map, it does not limit what a determined client can still do:
- Disable field suggestions (see below).
- Depth and complexity limiting to stop expensive nested queries.
- Persisted queries / allow-lists so production accepts only known operation hashes.
- Authentication and field-level authorization — the real boundary. Introspection on or off, a field nobody is allowed to resolve is safe; a field anyone can resolve is exposed regardless.
Audit what your schema would reveal by loading the SDL into the GraphQL schema visualizer — seeing the type graph laid out makes accidentally-public mutations and sensitive fields obvious.
The catch: field suggestions
Turning off introspection feels like closing the door, but most GraphQL servers leave a window open. By default they return helpful validation errors on typos:
Cannot query field "emial" on type "User". Did you mean "email"?
That “Did you mean” probes the schema one field at a time even with introspection fully disabled — an attacker scripts a dictionary of likely names and reconstructs much of the schema from the suggestions. graphql-js exposes this through didYouMean, and several frameworks let you suppress suggestion text in production. Treat field-suggestion hardening as part of the same task as disabling introspection, not a separate one (OWASP GraphQL Cheat Sheet).
Caveats
- Disabling introspection breaks your own tooling against production (IDE autocomplete, schema diffing). Keep it on in staging, and use schema registries / CI artifacts to track the production schema instead of querying it live.
- Introspection is not authentication. A public schema with proper field authorization can be safer than a hidden schema with none.
- Some gateways and CDNs cache introspection responses; clearing the flag in the origin does not purge an already-cached schema. Invalidate caches after the change.