How to Prevent Insecure GraphQL APIs in Vibe-Coded Apps
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:
Let's dive into the concrete steps to secure your GraphQL API.
What to check first
Before you write any code, run these checks:
User type include password, creditCard, or ssn? If so, remove them.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-limitThen 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-limitimport 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-complexityimport { 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:
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.