Skip to content

VNX-GQL-001: GraphQL introspection enabled in production

Reports GraphQL servers that hard-code introspection, GraphiQL or Playground on (introspection: true, graphiql: true, playground: true in JavaScript/TypeScript; graphiql=True or introspection=True in Python GraphQL views), exposing the full schema.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-GQL-001
Severity Medium
SARIF level warning
Kind sast
Languages JavaScript / TypeScript, Python
CWE CWE-200
CAPEC CAPEC-116
ATT&CK T1590
CWSS vector CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C
Tags graphql information-disclosure misconfiguration
Source internal/sast/rules/vnx-gql-001.rego

Overview

This rule detects GraphQL servers where introspection is explicitly enabled (introspection: true) or the GraphiQL IDE is left active (graphiql: true). GraphQL introspection is a built-in feature that lets any client query the server for a complete description of its schema, every type, query, mutation, field name, and argument. While useful during development, leaving introspection enabled in production provides a detailed reconnaissance map to attackers.

Introspection is enabled by default in most GraphQL frameworks, including Apollo Server (prior to v3), express-graphql, graphene-django, and graphql-java. You must explicitly disable it in production deployments.

OWASP ASVS v4: V13.4.1, Verify that a query allowlist or a combination of depth limiting and amount limiting is in place for GraphQL APIs, and that introspection is disabled in production environments.

Why This Matters

Attackers routinely send introspection queries as an early reconnaissance step against any GraphQL endpoint they discover. With the full schema, an attacker immediately knows:

  • Every field name your data model uses, potentially revealing internal naming like internalAdminFlag, deletedAt, or stripeCustomerId
  • Every mutation available, including dangerous admin operations
  • Argument types and structures that help craft injection payloads
  • Deprecated fields that may lack the same hardening as current fields

Tools like graphql-voyager and InQL are specifically designed to visualise and attack GraphQL APIs using introspection results. Even after disabling introspection, note that field suggestion (covered separately in VNX-GQL-004) can still allow schema enumeration, both controls should be applied together.

What Gets Flagged

// FLAGGED: introspection: true in Apollo Server config
const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: true,   // <-- flagged
});
// FLAGGED: GraphiQL enabled in express-graphql
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,        // <-- flagged
}));
# FLAGGED: GraphiQL on a flask-graphql / graphene view, and Strawberry
view_func=GraphQLView.as_view("graphiql", schema=schema, graphiql=True)
graphql_app = GraphQLRouter(schema, graphiql=True)

Not flagged: introspection: isDev, graphiql: process.env.NODE_ENV === 'development', graphiql=DEBUG, ...(isDev ? { introspection: true } : {}), a view mounted inside if settings.DEBUG: or if (process.env.NODE_ENV !== 'production'), the same key in a file that builds no GraphQL server, and test files.

Remediation

Apollo Server

Apollo Server v3+ automatically disables introspection when NODE_ENV is production. In all versions, disable it explicitly using an environment check:

// SAFE: introspection only in non-production environments
const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: process.env.NODE_ENV !== 'production',
});

express-graphql

// SAFE: GraphiQL only in development
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: process.env.NODE_ENV === 'development',
}));

Graphene (Python / Django)

Disable via Django settings, or use a middleware that intercepts __schema queries:

# settings.py
GRAPHENE = {
    'SCHEMA': 'myapp.schema.schema',
    'SCHEMA_INTROSPECTION_ENABLED': False,
}

Or with a custom middleware for non-Django setups:

from graphql import GraphQLError

class DisableIntrospectionMiddleware:
    def resolve(self, next, root, info, **kwargs):
        if info.field_name.startswith('__'):
            raise GraphQLError("Introspection is disabled in production.")
        return next(root, info, **kwargs)

graphql-java

Use the built-in NoIntrospectionGraphqlFieldVisibility to strip introspection from the schema entirely:

GraphQLSchema schema = GraphQLSchema.newSchema()
    .query(queryType)
    .fieldVisibility(NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY)
    .build();

Defence in Depth: Pair with Depth Limiting

Disabling introspection does not prevent abuse of queries attackers have already discovered. Add depth limiting as a complementary control:

// Install: npm install graphql-depth-limit
import depthLimit from 'graphql-depth-limit';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: process.env.NODE_ENV !== 'production',
  validationRules: [depthLimit(7)],
});

If you need introspection for internal tooling, require authentication for introspection queries rather than enabling it globally. Some GraphQL security plugins (such as GraphQL Armor) support role-based introspection access.

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-gql-001
// nosec vnx-gql-001

Or record an ignore rule: vulnetix ignore add --rule vnx-gql-001 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-gql-001.rego, cli/internal/sast/docs/vnx-gql-001.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.