All guides
Medium severityAPI Security

Public GraphQL introspection: why it's a problem and how to turn it off

GraphQL introspection is the built-in query that returns your API's complete schema: every type, field, argument, and mutation. It's essential in development but shouldn't be reachable in production for private APIs. Left on, it's a map of your entire backend attack surface.

What it is

Every GraphQL server implements a small set of meta-fields, most notably __schema and __type. A single POST to /graphql with `query { __schema { types { name fields { name } } } }` returns the full type definition.

Tools like GraphiQL, Apollo Sandbox, and the Playground UI depend on introspection to autocomplete queries and render the schema explorer. In development that's what you want; in production, on a private API, it means anyone in the world can download the same map.

Why it matters

The schema names your models, your relationships, and every mutation you expose — including admin, internal, and deprecated fields that were never meant to be discovered from the outside. Attackers use it to build a targeted list of operations to probe for auth-bypass, IDOR, and injection bugs.

Even if every field is authorized correctly, hidden mutations like refundOrder, impersonateUser, or exportBackup shouldn't advertise themselves. Introspection turns the attacker's reconnaissance phase from hours of guessing into a single request.

How to fix it

Disable introspection in Apollo Server

Turn introspection off when NODE_ENV is production. Keep it on locally so your team can still explore the schema in the Sandbox.

import { ApolloServer } from "@apollo/server";

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: process.env.NODE_ENV !== "production",
});
Disable it in GraphQL Yoga / graphql-http

Yoga has a first-class plugin; graphql-http exposes a validation rule from graphql-js. Both stop introspection queries before they run.

// GraphQL Yoga
import { createYoga } from "graphql-yoga";
import { useDisableIntrospection } from "@graphql-yoga/plugin-disable-introspection";

export const yoga = createYoga({
  schema,
  plugins: process.env.NODE_ENV === "production" ? [useDisableIntrospection()] : [],
});

// graphql-http (or express-graphql)
import { NoSchemaIntrospectionCustomRule } from "graphql";

const validationRules = process.env.NODE_ENV === "production"
  ? [NoSchemaIntrospectionCustomRule]
  : [];
Remove the schema-explorer UIs from production

GraphiQL, Playground, and Apollo Sandbox exist to help humans explore the schema — they don't belong on a public production endpoint. Gate them behind an auth check or don't mount them at all in the production build.

Prefer authorization over blocklists

Disabling introspection is a hardening step, not access control. Every field still needs proper authorization at the resolver level — otherwise anyone who guesses a field name can still call it. Treat introspection as one layer, not the layer.

FAQ

Isn't hiding the schema just security through obscurity?

Partly, yes — a determined attacker can still brute-force field names. But making reconnaissance cheap and complete is worth denying. Every commercial GraphQL API guide and the OWASP GraphQL cheat sheet recommend disabling introspection in production.

What about a public API where I want developers to explore the schema?

Ship the schema deliberately — publish a Schema Definition Language file, host a docs site, or expose introspection only after authentication. That's very different from an anonymous POST returning everything.

How does AppSafe detect this?

AppSafe POSTs a small introspection query to /graphql and /api/graphql and looks for a __schema response with a 200 status. If it comes back, it's flagged at medium severity in the API Security category.

Is your app affected?

AppSafe checks for this and dozens of other issues in one free scan.

Scan my app free