Back to Blog
2026-05-02

Your GraphQL API Is Handing Attackers a Blueprint: Introspection, Depth Limits, and the Query That Crashed Your Server

74% of organizations had an API-related breach last year. GraphQL's introspection feature is giving attackers the complete schema map they need to plan attacks—before you've even noticed the reconnaissance phase.

Your GraphQL API Is Handing Attackers a Blueprint: Introspection, Depth Limits, and the Query That Crashed Your Server

High Risk: Schema Exposure via GraphQL Introspection

Picture this: you built a GraphQL API. You added authentication. You spent two weeks getting the authorization layer right. You tested every mutation and query. You shipped it.

Now an attacker sits down at their terminal. They send a single query—{ __schema { types { name fields { name type } } } }—and within thirty seconds, they have your complete schema. Every entity, every relationship, every field name, every data type. They know your database structure before they've touched a single piece of sensitive data.

That's not a hypothetical. That's GraphQL's introspection feature doing exactly what it was designed to do.

74% of organizations experienced at least one API-related data breach in the past year. APIs now account for over 30% of all data breaches, up from under 20% two years ago. GraphQL APIs, with their flexible querying and notoriously permissive default configurations, are a growing portion of that attack surface.

The problem isn't that introspection exists. The problem is that nobody disables it in production.

What Introspection Actually Leaks

GraphQL introspection is a first-class feature. It's how IDEs power autocomplete, how documentation tools generate API references, how teams build internal tooling. It is genuinely useful during development.

In production, it's a reconnaissance goldmine.

An unauthenticated introspection query reveals:

  • Every type name and field in your schema, including internal or admin-only entities
  • Relationship graphs between entities, showing how data connects
  • Enum values, which often encode business logic (order statuses, user roles, subscription tiers)
  • Input type structures, which tell attackers exactly what payload format to use
  • Deprecated fields, which frequently point to old authorization logic that's no longer maintained

The pattern is consistent: introspection transforms a "figure out the API" phase from a multi-day effort into a thirty-second automated query. Attackers use introspection output to map your data model, identify writable mutations, and craft targeted attacks without ever triggering your rate limits with noise.

CVE-2025-59145, disclosed in late 2025, takes this further. It demonstrates that GraphQL query depth limits—a common mitigation against expensive nested queries—can be bypassed through aliased fields. An attacker chains field aliases to simulate deep query nesting while keeping the literal depth counter artificially low. The result: depth limits pass, but the actual query execution consumes resources as if the limit didn't exist.

CVE-2025-59145: Depth Limit Bypass via Field Aliasing

Query depth limiting is a standard GraphQL defense against resource exhaustion. CVE-2025-59145 showed that this control can be circumvented by using field aliases to replicate nested query patterns. A GraphQL server enforcing a depth limit of 5 could be sent a query with literal depth of 3—passing the check—while field aliases cause the resolver to execute operations equivalent to depth 15 or higher. The vulnerability affects multiple GraphQL server implementations that rely on naive depth counters.

The Stack That Makes This Worse

Most GraphQL deployments aren't doing anything to limit introspection exposure. Here's the typical setup:

// This is what most Apollo Server configs look like out of the box
const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: true, // Enabled by default in development
  // No validation plugins
  // No depth limiting
  // No query complexity analysis
});

No plugins. No schema visibility rules. No production-grade hardening. Just the raw schema exposed to whoever wants it.

The official GraphQL documentation acknowledges the problem: "Depending on your requirements, demand control can be implemented in many ways in GraphQL, including trusted documents, pagination of list fields, depth limiting, breadth/batch limiting, and rate limiting."

Notice the framing: "depending on your requirements." The spec doesn't force you to do any of this. The defaults are permissive, and permissive defaults in security-critical infrastructure is how you get breaches.

Hardening Your GraphQL Endpoint

The fix isn't complicated, but it requires actually implementing controls instead of relying on defaults.

Step one: disable introspection in production. This is the single highest-value change. If your clients don't need live schema discovery in production, turn it off.

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: process.env.NODE_ENV === 'development',
  plugins: [
    // Step two: add depth limiting
    {
      async didResolveOperation({ operation }) {
        const depth = calculateDepth(operation);
        if (depth > 10) {
          throw new GraphQLError('Query too deep', {
            extensions: { code: 'QUERY_TOO_DEEP' }
          });
        }
      }
    },
    // Step three: add query complexity analysis
    {
      async didResolveOperation({ operation }) {
        const complexity = calculateComplexity(operation);
        if (complexity > 1000) {
          throw new GraphQLError('Query too complex', {
            extensions: { code: 'QUERY_TOO_COMPLEX' }
          });
        }
      }
    }
  ]
});

Step two: implement query complexity analysis alongside depth limiting. CVE-2025-59145 bypassed depth limits using aliases. Complexity analysis evaluates the actual resolver execution cost, not just the query structure. A query that aliases the same field ten times still executes ten resolver calls—complexity analysis catches that.

Step three: add field-level permissions for sensitive operations. Don't assume that because introspection is disabled, attackers can't discover your mutation names. They will enumerate common patterns (createUser, updatePaymentMethod, deleteAccount). Authorization belongs at the resolver layer, not just at the API boundary.

const resolvers = {
  Mutation: {
    updateUserRole: async (parent, { userId, newRole }, context) => {
      // Authorization check - not just authentication
      if (!context.user || context.user.role !== 'ADMIN') {
        throw new GraphQLError('Insufficient permissions', {
          extensions: { code: 'FORBIDDEN' }
        });
      }
      // Business logic
      return db.users.update(userId, { role: newRole });
    }
  }
};

Step four: apply rate limiting at the API layer. GraphQL's single-endpoint architecture means traditional per-route rate limiting doesn't apply. You need middleware that tracks request frequency per IP or per authenticated user, regardless of the query shape.

74%
of organizations experienced an API-related breach in the past year
30%+
of all data breaches now involve API vulnerabilities, up from under 20% two years ago
30 sec
to extract a complete schema via introspection query using standard tooling
CVE-2025-59145
demonstrated depth limits bypassable via field aliasing in multiple GraphQL servers

What's Actually at Stake

When attackers have your schema, they have the map. They know which mutations exist, what inputs they accept, what data types you're using. They don't need to fuzz or guess. The reconnaissance phase is over before you've seen a single anomalous request in your logs.

Then the abuse starts. Credential stuffing through authentication endpoints. Data enumeration through nested queries. Business logic abuse through mutations that execute in unexpected sequences. Resource exhaustion through expensive resolver chains that your depth limiter doesn't catch because the depth counter doesn't understand what aliases actually do.

The fix is not optional. Disable introspection in production. Implement depth limiting and query complexity analysis together—neither is sufficient alone. Add authorization at the resolver level, not just at the API boundary. Rate limit at the middleware layer. This is not hardening; this is basic GraphQL deployment hygiene that the documentation makes sound optional but attackers will exploit as mandatory.

You shipped the GraphQL API. The reconnaissance phase already ran. The question is whether you're going to do something about it before the actual attack starts.


Debug Webhooks Locally

Building webhook integrations? Use our debugger to inspect payloads, verify signatures, and test endpoints without exposing your dev environment.

Open Webhook Debugger →
Share this: