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.
Table of Contents
- 1. What introspection technically means
- 2. What information introspection actually exposes
- 3. How attackers actually use introspection data
- 4. Why disabling it cannot be the only measure
- 5. Disabling introspection server-side
- 6. Environment-dependent configuration: dev vs. production
- 7. Still giving internal tools access
- 8. Alternatives: persisted queries and schema registry
- 9. Introspection strategies compared
- 10. Summary
- 11. FAQ
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.