← All posts
GraphQLvibe codingAI securityAPI securityGraphQL vulnerabilities

How to Prevent Insecure GraphQL APIs in Vibe-Coded Apps

O

OverMCP Team

Quick answer

To prevent insecure GraphQL APIs in vibe-coded apps, you must implement query depth limiting, rate limiting, and proper authentication from the start. AI coding tools often generate GraphQL resolvers that expose entire database schemas, making it easy for attackers to extract sensitive data. Use libraries like graphql-depth-limit and graphql-rate-limit to secure your API immediately.

Why GraphQL Security Matters for Vibe-Coded Apps

GraphQL is a popular choice for modern apps because it lets clients request exactly the data they need. But when AI coding tools like Cursor or Bolt.new generate your GraphQL schema, they often prioritize speed over security. The result? An API that exposes sensitive fields, allows deep nested queries, and has no rate limiting. This is a goldmine for attackers.

In a vibe-coded app, you might have a single GraphQL endpoint that powers your frontend. If that endpoint isn't secured, an attacker can craft queries to:

  • Extract all user emails and passwords (if exposed)
  • Perform denial-of-service attacks with deeply nested queries
  • Access fields you never intended to expose
  • Let's dive into the concrete steps to secure your GraphQL API.

    What to check first

    Before you write any code, run these checks:

  • [ ] Disable introspection in production – Introspection lets anyone see your full schema. Turn it off.
  • [ ] Check for exposed fields – Does your User type include password, creditCard, or ssn? If so, remove them.
  • [ ] Verify authentication – Is every resolver protected? Or can an unauthenticated user query any data?
  • [ ] Limit query depth – Can someone send a query that nests 10 levels deep? That's a DoS vector.
  • [ ] Implement rate limiting – Is there a limit on requests per IP or per user? Without it, brute-force attacks are trivial.
  • Use a free AI app security scanner to automatically detect these issues in your deployed app.

    Step-by-step fix

    1. Disable introspection in production

    In Apollo Server, set introspection: false:

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

    In Yoga GraphQL, use:

    const server = createYoga({
      schema,
      graphqlEndpoint: '/graphql',
      introspection: process.env.NODE_ENV !== 'production',
    });

    2. Add query depth limiting

    Install graphql-depth-limit:

    npm install graphql-depth-limit

    Then apply it to your server:

    import depthLimit from 'graphql-depth-limit';
    
    const server = new ApolloServer({
      typeDefs,
      resolvers,
      validationRules: [depthLimit(5)], // max depth of 5
    });

    3. Implement rate limiting

    Use graphql-rate-limit (or a generic rate limiter like express-rate-limit):

    npm install graphql-rate-limit
    import rateLimit from 'graphql-rate-limit';
    
    const rateLimitDirective = rateLimit({
      identifyContext: (ctx) => ctx.user?.id || ctx.ip,
    });
    
    const typeDefs = `
      directive @rateLimit(limit: Int!, duration: Int!) on FIELD_DEFINITION
    
      type Query {
        users: [User] @rateLimit(limit: 10, duration: 60)
      }
    `;

    4. Add authentication to every resolver

    Never trust that a field is safe. Use a middleware or directive:

    const typeDefs = `
      directive @auth on FIELD_DEFINITION
    
      type Query {
        user(id: ID!): User @auth
      }
    `;
    
    const resolvers = {
      Query: {
        user: async (_, { id }, context) => {
          if (!context.user) throw new AuthenticationError('Not authenticated');
          return getUser(id);
        },
      },
    };

    5. Validate input for nested queries

    Use graphql-query-complexity to limit the total cost of a query:

    npm install graphql-query-complexity
    import { createComplexityRule, simpleEstimator } from 'graphql-query-complexity';
    
    const server = new ApolloServer({
      typeDefs,
      resolvers,
      validationRules: [
        createComplexityRule({
          estimators: [simpleEstimator({ defaultComplexity: 1 })],
          maximumComplexity: 100,
        }),
      ],
    });

    Common mistakes

    1. Exposing the entire database schema

    AI tools often generate GraphQL types that mirror your database tables exactly. This means if your User table has a passwordHash field, it might end up in the GraphQL schema. Attackers can then query:

    query {
      users {
        email
        passwordHash
      }
    }

    Fix: Always define separate types for your API. Never use auto-generated schemas in production without manual review.

    2. Leaving introspection on in production

    Introspection is incredibly useful during development, but forgetting to disable it in production gives attackers a complete map of your API. They can see every type, field, and argument. This is the first thing a malicious actor looks for.

    Fix: Set introspection: false in production, or restrict it to certain IPs if needed. Use a security headers checker to verify your deployment is locked down.

    3. No rate limiting on mutations

    Attackers can brute-force login mutations or spam your database with repeated mutations. Without rate limiting, they can try unlimited password guesses or create thousands of fake accounts.

    Fix: Apply rate limiting to all mutations, especially login, signup, and password reset.

    4. Ignoring batch queries

    GraphQL allows clients to send multiple queries in a single request. An attacker can send a batch of expensive queries to overwhelm your server. Without batch limiting, a single request can cause a DoS.

    Fix: Limit the number of queries per request (e.g., max 5). Use graphql-rate-limit or a custom validation rule.

    How to automate these checks

    Manually reviewing your GraphQL schema is tedious and error-prone. Use a tool like OverMCP to automatically scan your deployed app for:

  • Introspection enabled
  • Exposed sensitive fields
  • Missing authentication
  • Query depth and complexity issues
  • It integrates with Vercel, Netlify, and GitHub, so you can scan on every deploy. For continuous security, consider continuous security monitoring to catch regressions.

    FAQ

    How do I know if my GraphQL API is insecure?

    Run a quick manual test: send an introspection query to your endpoint. If it returns the schema, you're exposed. Also check if you can query fields like password or creditCard. Use a free scanner like OverMCP to automate this.

    Can I secure GraphQL without a library?

    Yes, but it's harder. You can manually validate query depth and complexity in your resolvers, but libraries like graphql-depth-limit and graphql-query-complexity are well-tested and easier to implement. For auth, you need to wrap every resolver with authentication checks.

    What should I do if my app is already deployed with a vulnerable GraphQL API?

    Immediately disable introspection, add authentication to all resolvers, and implement rate limiting. If you have exposed sensitive fields, rotate any secrets (API keys, passwords) that might have been leaked. Use a secret leak scanner to check your codebase for exposed keys.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free