API Security
GraphQL
API Security
Authorization
DoS Protection

GraphQL Security Vulnerabilities: The Complete Guide for 2025

SecureCodeReviews Team
January 28, 2025
14 min read
Share

Why GraphQL Security Is Different

GraphQL has become the API standard for modern frontends, powering companies like GitHub, Shopify, and Airbnb. But its flexible query language creates a fundamentally different attack surface than REST APIs.

Attack VectorREST APIGraphQL
Data enumerationLimited by endpointsIntrospection reveals entire schema
DoS via queriesRate limit per endpointNested queries can exponentially grow
Batching attacksOne operation per requestMultiple operations in single request
AuthorizationPer-endpoint middlewarePer-field resolver logic required
Information disclosureFixed response shapesClient controls what's returned

Key insight: The same flexibility that makes GraphQL great for developers makes it dangerous without proper security controls.


Vulnerability #1: Introspection Enabled in Production

GraphQL introspection lets anyone query your entire API schema — every type, field, mutation, and relationship.

❌ Vulnerable Configuration

// Apollo Server — introspection ON by default
const server = new ApolloServer({
  typeDefs,
  resolvers,
  // No introspection setting = enabled by default!
});

What Attackers See

# This query dumps your entire schema
{
  __schema {
    types {
      name
      fields {
        name
        type { name }
      }
    }
    mutationType {
      fields {
        name
        args { name type { name } }
      }
    }
  }
}

An attacker now knows every data model, relationship, mutation, and argument — a complete roadmap for exploitation.

✅ Fixed Configuration

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

Vulnerability #2: Query Depth Attacks (GraphQL Bombs)

Nested relationships let attackers craft queries that explode exponentially.

❌ Vulnerable Query

# Each level multiplies the database load
query {
  users {
    posts {
      comments {
        author {
          posts {
            comments {
              author {
                posts {  # 7 levels deep — hundreds of DB queries
                  title
                }
              }
            }
          }
        }
      }
    }
  }
}

A single request like this can trigger thousands of database queries (the N+1 problem, multiplied).

✅ Fix: Query Depth Limiting

import depthLimit from 'graphql-depth-limit';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [depthLimit(5)], // Max 5 levels deep
});

Vulnerability #3: Query Complexity / Cost Attacks

Even shallow queries can be expensive if they request massive datasets.

❌ Expensive Query

query {
  allUsers(first: 10000) {    # 10,000 users
    orders(first: 100) {       # × 100 orders each
      items {                   # × N items each
        product {
          reviews(first: 50) {  # × 50 reviews each
            text
          }
        }
      }
    }
  }
}

✅ Fix: Query Cost Analysis

import { createComplexityLimitRule } from 'graphql-validation-complexity';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    createComplexityLimitRule(1000, {
      scalarCost: 1,
      objectCost: 10,
      listFactor: 20,
      onCost: (cost) => {
        console.log('Query cost:', cost);
      },
    }),
  ],
});

Vulnerability #4: Batching Attacks for Brute Force

GraphQL allows sending multiple operations in a single HTTP request — perfect for brute-forcing.

❌ Brute Force via Batching

[
  { "query": "mutation { login(email: \"admin@example.com\", password: \"password1\") { token } }" },
  { "query": "mutation { login(email: \"admin@example.com\", password: \"password2\") { token } }" },
  { "query": "mutation { login(email: \"admin@example.com\", password: \"password3\") { token } }" },
  // ... 1000 more attempts in a single request
]

Traditional rate limiting (per request) won't catch this — it's one HTTP request with 1,000 login attempts.

✅ Fix: Limit Batch Size + Per-Operation Rate Limiting

// Limit batch size
app.use('/graphql', (req, res, next) => {
  if (Array.isArray(req.body) && req.body.length > 5) {
    return res.status(400).json({
      error: 'Batch limit exceeded. Maximum 5 operations per request.'
    });
  }
  next();
});

// Per-operation rate limiting on sensitive mutations
const rateLimitDirective = {
  login: { window: '15m', max: 5 },
  resetPassword: { window: '1h', max: 3 },
  createUser: { window: '1h', max: 10 },
};

Vulnerability #5: Missing Field-Level Authorization

With REST, you authorize at the endpoint level. With GraphQL, every field needs authorization.

❌ Vulnerable Resolver

const resolvers = {
  Query: {
    user: (_, { id }) => User.findById(id), // No auth check!
  },
  User: {
    email: (user) => user.email,          // Anyone can see emails
    ssn: (user) => user.ssn,              // Anyone can see SSNs!
    salary: (user) => user.salary,         // Anyone can see salaries!
    internalNotes: (user) => user.notes,   // Internal data exposed
  },
};

✅ Fixed: Field-Level Auth

const resolvers = {
  Query: {
    user: (_, { id }, context) => {
      if (!context.user) throw new AuthenticationError('Login required');
      return User.findById(id);
    },
  },
  User: {
    email: (user, _, context) => {
      if (context.user.id === user.id || context.user.role === 'admin')
        return user.email;
      return null; // Mask for other users
    },
    ssn: (user, _, context) => {
      if (context.user.role !== 'admin')
        throw new ForbiddenError('Insufficient permissions');
      return user.ssn;
    },
    salary: (user, _, context) => {
      if (context.user.role !== 'hr' && context.user.id !== user.id)
        return null;
      return user.salary;
    },
  },
};

GraphQL Security Checklist

ControlTool/LibraryPriority
Disable introspection in productionApollo config, graphql-disable-introspectionCritical
Query depth limitinggraphql-depth-limitCritical
Query cost analysisgraphql-validation-complexityHigh
Batch limitingCustom middlewareHigh
Field-level authorizationgraphql-shield, custom directivesCritical
Persisted queries onlyApollo automatic persisted queriesHigh
Input validationCustom scalars, joi, zodHigh
Rate limiting per operationgraphql-rate-limitMedium
Logging & monitoringApollo Studio, custom pluginsMedium

Free Security Review

Worried about your GraphQL API security? We review real code — not just config files. Request a free 20-line code review →


Published by the SecureCodeReviews.com team — helping development teams ship secure GraphQL APIs since 2024.

Advertisement