Disabling GraphQL Introspection in Production: Understanding the Security Risks
AI generated
{ }
type
GraphQL · Introspection · Security · Production
Disabling GraphQL Introspection in Production
an open schema is an open map

Introspection is one of the most elegant properties of GraphQL, letting you query the entire schema through a request. That exact feature is what makes it risky in production: anyone who knows the API can read internal field names, mutations with access to sensitive data, and even deprecation comments, without guessing a single endpoint. Anyone who does not deliberately secure introspection hands attackers the full map of their own API.

16 min read Schema introspection · __schema · Middleware Apollo Server · GraphQL Yoga · Magento GraphQL

1. What introspection technically means

GraphQL introspection is a fixed part of the GraphQL specification and lets you query the schema through special meta fields such as __schema and __type. Every type, every field, every argument and every description stored in the schema can be read programmatically this way, without knowing the documentation or the source code. This exact property is what makes tools like GraphiQL, Apollo Studio and IDE autocompletion possible in the first place.

The catch: GraphQL introspection is enabled by default in most server implementations, including production, and does not distinguish between an internal developer team and an anonymous attacker on the internet. Anyone who can reach a GraphQL endpoint can generally also query its entire schema, unless someone has explicitly restricted this feature. This article explains what risks open introspection carries in production and how to disable it cleanly.


# A minimal introspection query that reveals the entire schema structure
query IntrospectionQuery {
  __schema {
    types {
      name
      description
      fields {
        name
        description
        args { name type { name } }
        deprecationReason
      }
    }
    mutationType {
      fields { name description }
    }
  }
}

2. What information introspection actually exposes

A full GraphQL introspection response contains far more than just field names. It includes every available query, mutation and subscription with their arguments and return types, every enum value, every interface and union relationship, and all the description text developers stored as documentation in the schema. Deprecation comments are especially sensitive: a field like @deprecated(reason: "Use adminOverridePrice instead, internal only") not only tells an attacker that an old field exists, it hands over the name of its replacement along with a hint about its sensitive nature.

Mutations meant only for internal admin interfaces also show up in the GraphQL introspection response, even if they are not linked anywhere in the frontend. Security by obscurity, relying on nobody guessing a particular mutation name, fundamentally does not work with GraphQL, because introspection exposes exactly those names on request. Without field-level authorization, an exposed internal field is directly attackable the moment its name is known.

3. How attackers actually use introspection data

In practice, GraphQL introspection is usually the first step in the reconnaissance phase of an attack. Automated tools like InQL or GraphQL Voyager download the entire schema and visualize it as a searchable tree, including all types and relationships. This maps an attack surface in minutes that would have taken days or weeks of guessing endpoints without introspection.

Concretely, attackers use the gathered information for targeted batch attacks on expensive fields, see query complexity attacks, for finding mutations without adequate authorization, and for enumeration attacks on fields with predictable IDs. Mapping internal enum values, say internal status codes of an order process, can also reveal business logic that was never meant to be public. GraphQL introspection itself is therefore not a direct exploit, it is the foundation more targeted attacks build on.

4. Why disabling it cannot be the only measure

An important point upfront: disabling GraphQL introspection is not a substitute for clean authorization. An attacker who cannot query the schema via introspection can still guess fields, extract them from JavaScript bundles in the frontend, or infer them from error messages. Disabling introspection reduces the attack surface but does not eliminate it, and it does not replace field-level authorization that actually checks whether a user is allowed to read a field or execute a mutation.

The correct order is therefore: first ensure clean authorization at the resolver level, then disable GraphQL introspection in production as an additional layer of defense. Anyone who relies exclusively on turning off introspection without checking the underlying permissions has merely reduced discoverability, not fixed the actual vulnerability.

5. Disabling introspection server-side

Apollo Server offers a built-in option to fully turn off GraphQL introspection, usually tied to an environment variable. GraphQL Yoga and other servers offer comparable configuration options, or allow removing the introspection fields via a plugin that filters the meta fields __schema and __type out of every incoming query before validation. It matters to enforce the disabling not just by hiding it in the frontend client, but actually server-side, since an attacker addresses the API directly and does not use a client.

For Magento environments with the native GraphQL module, introspection can be restricted through a custom middleware or plugin on the GraphQL controller, since Magento itself does not ship a native configuration option to turn off introspection. A robust solution checks incoming queries for __schema or __type occurrences and rejects those requests before they reach the regular query executor.


// server.js — disabling introspection in production with Apollo Server
import { ApolloServer } from '@apollo/server';
import { ApolloServerPluginLandingPageDisabled } from '@apollo/server/plugin/disabled';

const isProduction = process.env.NODE_ENV === 'production';

const server = new ApolloServer({
  schema,
  introspection: !isProduction,
  plugins: isProduction ? [ApolloServerPluginLandingPageDisabled()] : [],
});

// yoga.js — GraphQL Yoga equivalent using a plugin
import { createYoga } from 'graphql-yoga';
import { useDisableIntrospection } from '@graphql-yoga/plugin-disable-introspection';

const yoga = createYoga({
  schema,
  plugins: isProduction ? [useDisableIntrospection()] : [],
});

6. Environment-dependent configuration: dev vs. production

A blanket disabling of GraphQL introspection across all environments is rarely a good idea, because developer teams depend on introspection in staging and local development, for example for automatic type generation in the frontend or IDE autocompletion. Common practice is a strict split by NODE_ENV or a comparable environment variable: introspection stays active in development and staging but is consistently turned off in production.

A common mistake is setting this environment variable incorrectly or forgetting it during deployment, so production servers accidentally run with GraphQL introspection enabled. An automated check in the CI pipeline that sends an introspection query against the production URL and stops the deployment process on a successful response reliably prevents exactly this configuration mistake, instead of relying on manual review.


# CI check — fail the pipeline if introspection is reachable in production
#!/usr/bin/env bash
set -euo pipefail

RESPONSE=$(curl -s -X POST https://api.mironsoft.de/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ __schema { types { name } } }"}')

if echo "$RESPONSE" | grep -q '"__schema"'; then
  echo "[FAIL] Introspection is enabled in production!" >&2
  exit 1
fi

echo "[OK] Introspection is disabled in production"

7. Still giving internal tools access

Simply switching off GraphQL introspection can break internal tools like an API monitoring dashboard or a schema diff check in the CI pipeline that themselves regularly run introspection queries. The practical solution is a separate, authenticated introspection endpoint or an IP allowlist or VPN-protected access, through which only internal tooling gets introspection access, while the public endpoint stays fully closed.

An even cleaner alternative is a separate schema registry like Apollo GraphOS or Hive, which stores the schema independently of the running production server and makes it available to internal tools through its own, authenticated API. This way, production GraphQL introspection never needs to stay open for tooling purposes, because the schema information comes from a separate, controlled source instead of being queried live from the production server.


// server.js — allowing introspection only for an internal, authenticated route
const isInternalTooling = (req) =>
  req.headers['x-internal-token'] === process.env.INTERNAL_TOOLING_TOKEN;

const server = new ApolloServer({
  schema,
  introspection: true, // validated per-request below, not globally
});

app.post('/graphql', async (req, res, next) => {
  const hasIntrospectionField = JSON.stringify(req.body).includes('__schema');
  if (hasIntrospectionField && !isInternalTooling(req)) {
    return res.status(403).json({
      errors: [{ message: 'Introspection is disabled for public clients' }],
    });
  }
  next();
});

8. Alternatives: persisted queries and schema registry

Persisted queries reduce the residual risk that remains even with disabled GraphQL introspection even further: instead of accepting arbitrary query strings, the server only allows a predefined list of query hashes registered at frontend build time. An attacker then cannot execute any new, arbitrary query against the API even with guessed field names, since only already-known query signatures are accepted.


{
  "extensions": {
    "persistedQuery": {
      "version": 1,
      "sha256Hash": "b1946ac92492d2347c6235b4d2611184"
    }
  },
  "variables": { "productId": "xyz789" }
}

If the hash is not found in the server-side registry, the server rejects the request, regardless of whether the underlying query would have been valid GraphQL syntax.

A schema registry complements this approach organizationally: schema changes are versioned centrally, breaking changes are caught before deployment, and teams retain full insight into schema history through a separate, authorized system, despite disabled GraphQL introspection in production. For teams with multiple consumers of the API, say several frontend applications, the combination of persisted queries and a schema registry is the most robust solution.

9. Introspection strategies compared

The overview below ranks common approaches to handling GraphQL introspection by protective effect and complexity.

Strategy Protective effect Impairs internal tools Effort
Introspection active everywhere None No None
Blanket disabled Medium Yes Low
Disabled in production only High No Low
Disabled + persisted queries + registry Very high No High

For most teams, environment-dependent disabling of GraphQL introspection in production is the pragmatic standard, complemented by persisted queries once multiple frontend clients consume the same API and an extra security layer is justified.

Mironsoft

GraphQL security, API hardening and Magento integrations

Is your GraphQL API running with open introspection in production?

We audit your endpoint, disable introspection cleanly per environment and set up persisted queries and field-level authorization where it actually matters.

Security check

Checking whether introspection and other metadata are publicly reachable

Configuration

Clean environment-based disabling without blocking internal tools

CI safeguards

Automated checks that stop misconfigurations before deployment

10. Summary

GraphQL introspection is a powerful developer feature that in production unintentionally exposes the entire schema, including internal fields, mutations and deprecation notes. Attackers use this information as a reconnaissance base for more targeted attacks like query complexity attacks or finding insufficiently protected mutations. Disabling introspection does not replace clean authorization, but it substantially reduces the attack surface and should therefore be consistently applied to production environments.

The most pragmatic implementation is an environment-dependent configuration: introspection stays active in development and staging, but is turned off in production via an environment variable, with a CI check catching misconfigurations before deployment. Internal tools get a separate, authenticated access path or a dedicated schema registry when needed, so GraphQL introspection can stay fully closed in production without hindering your own development work.

Disabling GraphQL Introspection in Production — Key Takeaways

What introspection exposes

The entire schema including internal fields, mutations, enum values and deprecation comments, without guessing a single endpoint.

Not a substitute for authorization

Disabling reduces the attack surface but does not replace field-level authorization at the resolver level.

Configure per environment

Active in dev and staging, disabled in production, controlled via environment variable and secured with a CI check.

Complementary measures

Persisted queries and a schema registry close the gap further once multiple clients consume the same API.

11. FAQ: Disabling GraphQL Introspection in Production

1What is GraphQL introspection?
A fixed spec feature that makes the entire schema readable via __schema and __type queries.
2Why is this a risk?
It gives attackers a public map of all internal fields and mutations without any guessing.
3Is disabling it enough alone?
No, it does not replace field-level authorization, only reduces discoverability.
4How to disable in Apollo Server?
Via the built-in introspection option, usually tied to NODE_ENV.
5How for Magento GraphQL?
Via a custom middleware or plugin that rejects __schema/__type occurrences before execution.
6How do internal tools keep access?
Via a separate authenticated endpoint, IP allowlist, or a dedicated schema registry.
7What are persisted queries?
Only predefined query hashes are accepted, any new arbitrary query is blocked.
8How to check automatically?
CI check sends an introspection query against the production URL and fails the pipeline on success.
9Does it reveal deprecation comments?
Yes, including reason text that can directly name sensitive replacement fields.
10Disable in staging too?
Usually not needed, secure staging with basic auth or a VPN instead of blocking introspection.