~/blog

GraphQL introspection: what it exposes and when to turn it off

published

#graphql#security#api

A node graph of connected green dots on a dark background, with a magnifying glass revealing a highlighted pink subtree
Generated illustration

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 introspectionWith introspection
Attacker must brute-force field and type namesAttacker downloads the full schema in one query
Internal/deprecated fields are hidden by obscurity@deprecated(reason: "use newField") advertises history and intent
Mutation names unknownEvery mutation, including admin-ish ones, is listed with arguments
Input shapes unknownExact 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:

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

References