GraphQL introspection lets any client query your entire schema: every type, field, argument, and resolver. In development, this is essential -- it powers GraphiQL, Apollo Studio, and every GraphQL client that offers autocomplete. In production, it gives an attacker a complete map of your API surface: every data model, every relationship, every mutation that modifies data. Disabling it in production is a single configuration change that removes this reconnaissance capability.
DDactic finds introspection enabled in production in approximately 65% of assessed GraphQL endpoints. The justification is usually that "introspection doesn't expose data, only schema." This understates the risk: knowing that a mutation called deleteUser exists with an adminKey argument, or that a query called exportAllUserData exists with no apparent rate limiting, directly enables targeted attacks that would not be possible without schema knowledge.
What Introspection Exposes
A complete introspection query returns the full type system: all query types and their arguments, all mutation types and their required fields, all subscription types, custom scalar types that reveal data formats, deprecation notices that reveal legacy paths still present in the codebase, and descriptions that developers wrote for internal documentation purposes (sometimes including internal notes about limitations or security controls).
Disabling Introspection: Apollo Server v4
// Production introspection disable
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== 'production',
// Also block field suggestions (Prisma's suggestion feature)
// which partially reconstructs schema even without introspection
plugins: [
{
requestDidStart() {
return {
didResolveOperation({ request, document }) {
const operationNames = document.definitions
.filter(def => def.kind === 'OperationDefinition')
.map(def => def.name?.value);
if (request.query?.includes('__schema') ||
request.query?.includes('__type')) {
throw new Error('Introspection not allowed in production');
}
}
};
}
}
]
});
Disabling Introspection: graphql-go
// graphql-go — introspection disabled via DisableIntrospection option
schema := graphql.MustParseSchema(schemaString, &resolver{},
graphql.DisableIntrospection(),
)
Disabling Introspection: Hasura
# Hasura Cloud / Hasura Enterprise
# Set via environment variable or config.yaml
HASURA_GRAPHQL_ENABLE_INTROSPECTION=false
# Or via project config:
# config.yaml
version: 3
endpoint: https://your-hasura-app.hasura.app
admin_secret: your-admin-secret
introspection: false
Keeping Introspection Available for Trusted Clients
If your front-end development team needs introspection access in production (for schema download, code generation, etc.), the correct pattern is to gate introspection on a header value checked against an allowlist. Only requests including a valid X-Introspection-Key header with a value matching a secret stored in your secrets manager should receive introspection responses. This gives your team access while blocking unauthenticated schema enumeration.
Testing: Verify Introspection Is Disabled
# Test introspection query — should return an error, not schema data
curl -X POST https://api.example.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { queryType { name } } }"}'
# Expected response (disabled):
# {"errors":[{"message":"Introspection is disabled"}]}
# Unexpected response (still enabled):
# {"data":{"__schema":{"queryType":{"name":"Query"}}}}
Check Your GraphQL Configuration
DDactic's scanner tests introspection availability and reports the full schema footprint that an attacker would see.
Run a Free Scan