HIGH graphql introspectionexpressapi keys

Graphql Introspection in Express with Api Keys

Graphql Introspection in Express with Api Keys — how this specific combination creates or exposes the vulnerability

GraphQL introspection in an Express server exposes the full schema, including types, queries, and mutations. When API keys are used for authorization but introspection remains accessible without proper checks, an unauthenticated or low-privilege caller can enumerate the API surface. This combination creates a risk where an attacker can discover endpoints, query arguments, and response shapes, which can aid in crafting further attacks such as BOLA/IDOR or data exposure.

In Express, common patterns serve GraphQL via libraries like express-graphql or Apollo Server. If introspection is enabled (often the default in development) and the route does not validate the caller’s API key before resolving the introspection query, the API key mechanism is bypassed for schema discovery. For example, a route like /graphql that accepts POST bodies with { "query": "{ __schema { queryType { name } } }" } will return schema details even when the request includes an API key that is only validated after route entry or not validated for introspection operations specifically.

Consider an Express setup where API keys are checked via middleware, but the GraphQL handler does not gate introspection behind the same check:

const { graphqlHTTP } = require('express-graphql');
const schema = require('./schema');

app.post('/graphql', (req, res, next) => {
  const key = req.headers['x-api-key'];
  if (!validKeys.includes(key)) return res.status(401).send('Unauthorized');
  next();
}, graphqlHTTP({ schema, graphiql: true }));

If an attacker sends an introspection query without a valid API key to a misconfigured route, or if the GraphQL handler allows introspection regardless of the middleware outcome in certain error paths, the schema is returned. This can reveal queries like users(id: $id) or fields that should be protected, effectively mapping the attack surface for further exploitation such as IDOR.

The risk is elevated when combined with other unchecked operations. Introspection can expose arguments that are susceptible to injection or authorization flaws. For instance, if a user(id: ID!) query is exposed and lacks proper ownership checks, an attacker can iterate over IDs knowing the exact field names and types. middleBrick’s scans detect such exposure as part of its Authorization and Input Validation checks, highlighting the need to restrict introspection in production.

Api Keys-Specific Remediation in Express — concrete code fixes

To secure GraphQL introspection in Express when using API keys, ensure introspection is disabled or strictly gated by the same authorization checks applied to other operations. Do not rely on network-level obscurity or development-only settings in production.

1) Disable introspection in production builds:

const { graphqlHTTP } = require('express-graphql');
const schema = require('./schema');

app.post('/graphql', (req, res, next) => {
  const key = req.headers['x-api-key'];
  if (!validKeys.includes(key)) return res.status(401).send('Unauthorized');
  next();
}, graphqlHTTP({ schema, graphiql: false, introspection: false }));

Setting introspection: false ensures the __schema and __type root fields are unavailable regardless of authentication, eliminating enumeration via introspection.

2) Conditional introspection with API key validation inside the handler:

app.post('/graphql', (req, res) => {
  const key = req.headers['x-api-key'];
  if (!validKeys.includes(key)) return res.status(401).json({ errors: [{ message: 'Unauthorized' }] });

  const { query } = req.body || {};
  const isIntrospection = query && (query.includes('__schema') || query.includes('__type'));
  if (isIntrospection) {
    return res.status(403).json({ errors: [{ message: 'Introspection disabled' }] });
  }

  // proceed with validated key and business logic
  graphqlHTTP({ schema, graphiql: false })(req, res);
});

This pattern validates the API key first, then explicitly blocks introspection queries before they reach the GraphQL handler, ensuring that only authorized callers can perform operations and that schema discovery is not accidentally exposed.

3) Use Apollo Server with plugins for more structured control:

const { ApolloServer, gql } = require('apollo-server-express');
const { makeExecutableSchema } = require('@graphql-tools/schema');

const typeDefs = gql`
  type Query { hello: String }
`;
const resolvers = { Query: { hello: () => 'world' } };
const schema = makeExecutableSchema({ typeDefs, resolvers });

const server = new ApolloServer({
  schema,
  plugins: [{
    requestDidStart() {
      return {
        didResolveOperation({ request, operation }) {
          const key = request.http.headers.get('x-api-key');
          if (!validKeys.includes(key)) {
            throw new Error('Unauthorized');
          }
          if (operation.query && (operation.query.includes('__schema') || operation.query.includes('__type'))) {
            throw new Error('Introspection disabled');
          }
        }
      };
    }
  }]
});

await server.start();
server.applyMiddleware({ app });

With this setup, API key validation and introspection blocking are enforced at the GraphQL request level, aligning the security posture with the authorization model used across the API.

Finally, if you need limited introspection for tooling in specific environments (e.g., staging), restrict it by IP allowlist or require a special API key scope, and ensure the route is not exposed to the public internet. middleBrick’s scans can verify whether introspection remains exposed and map findings to frameworks like OWASP API Top 10 to prioritize remediation.

Related CWEs: dataExposure

CWE IDNameSeverity
CWE-200Exposure of Sensitive Information HIGH
CWE-209Error Information Disclosure MEDIUM
CWE-213Exposure of Sensitive Information Due to Incompatible Policies HIGH
CWE-215Insertion of Sensitive Information Into Debugging Code MEDIUM
CWE-312Cleartext Storage of Sensitive Information HIGH
CWE-359Exposure of Private Personal Information (PII) HIGH
CWE-522Insufficiently Protected Credentials CRITICAL
CWE-532Insertion of Sensitive Information into Log File MEDIUM
CWE-538Insertion of Sensitive Information into Externally-Accessible File HIGH
CWE-540Inclusion of Sensitive Information in Source Code HIGH

Frequently Asked Questions

Can API keys alone prevent GraphQL introspection leaks?
No. API keys must be enforced before the GraphQL handler runs, and introspection should be explicitly disabled or blocked; relying on keys alone is insufficient if the route returns schema when introspection queries are present.
How can I test whether my Express GraphQL endpoint leaks schema via introspection?
Send a POST request with {"query":"{ __schema { queryType { name } } }"} to your /graphql route both with and without a valid API key. If you receive schema details without a key or when introspection is not explicitly disabled, the endpoint is leaking schema information.