Skip to content
Back to skills

Apply Graphql Security

ASecurity

Use when building or securing a GraphQL API — limiting query depth and complexity, disabling introspection in production, enforcing field-level authorization, and preventing batching abuse.

  • 4 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added September 8, 2026
ai-agentsjavascriptpythongojavasqlnodedjangogitapidatabase

Works with

  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned September 8, 2026

npx -y skills add jeffreytse/grimoire-core --skill apply-graphql-security --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Apply Graphql Security?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Apply Graphql Security
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jeffreytse-apply-graphql-security/badge)](https://www.skillsdirectory.com/skills/jeffreytse-apply-graphql-security)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: apply-graphql-security
description: Use when building or securing a GraphQL API — limiting query depth and complexity, disabling introspection in production, enforcing field-level authorization, and preventing batching abuse.
source: 'OWASP GraphQL Security Cheat Sheet (owasp.org/www-project-cheat-sheets); OWASP API Security Top 10 2023; HackerOne GraphQL security research; Apollo Server security documentation'
tags: [security, owasp, graphql, api, authorization, rate-limiting, developer]
---

# Apply GraphQL Security

Secure GraphQL APIs by enforcing query depth limits, disabling introspection in production, implementing field-level authorization, and rate limiting by query complexity — preventing DoS via deeply nested queries and unauthorized data access via field enumeration.

## Why This Is Best Practice

**Adopted by:** OWASP GraphQL Security Cheat Sheet (2023) is the primary reference. HackerOne's GraphQL security research documents the most common GraphQL vulnerability classes found across bug bounty programs. Apollo Server (used by Airbnb, The New York Times, Expedia) provides built-in query depth limiting and complexity analysis. GitHub's GraphQL API enforces complexity limits (5,000 points per query) and requires authentication for all requests including introspection.
**Impact:** HackerOne's 2022 "Hacking GraphQL for Fun and Profit" found introspection enabled in production in 40% of GraphQL APIs tested — allowing attackers to map entire schemas. Deeply nested GraphQL queries (10+ levels) can cause O(n^k) database queries from a single HTTP request, enabling DoS with a single request. Shopify paid out multiple GraphQL-related bug bounties for missing field-level authorization allowing access to other merchants' data — direct object reference without per-field auth checks.
**Why best:** REST APIs use URL-based authorization (middleware checks each endpoint); GraphQL resolvers require per-field authorization because a single endpoint serves all queries. A REST security model applied to GraphQL (middleware on `/graphql`) allows a user with read access to `viewer { name }` to also query `viewer { paymentMethods { cardNumber } }` if field-level checks are missing.

Sources: OWASP GraphQL Security Cheat Sheet; HackerOne "Hacking GraphQL for Fun and Profit" (2022); Apollo Server documentation; GitHub GraphQL API documentation

## Steps

1. **Limit query depth and complexity — prevent DoS via nested queries**:

   ```python
   # Graphene (Python) with graphene-django
   from graphql import parse, validate
   from graphql.validation.rules import NoSchemaIntrospectionCustomRule

   # Install: pip install graphql-core graphene
   # Depth limit middleware
   class QueryDepthLimiter:
       MAX_DEPTH = 7

       def resolve(self, next, root, info, **args):
           if root is None:  # top-level only
               depth = self._get_query_depth(info.operation)
               if depth > self.MAX_DEPTH:
                   raise Exception(f"Query depth {depth} exceeds maximum {self.MAX_DEPTH}")
           return next(root, info, **args)

       def _get_query_depth(self, node, depth=0):
           max_depth = depth
           if hasattr(node, 'selection_set') and node.selection_set:
               for selection in node.selection_set.selections:
                   child_depth = self._get_query_depth(selection, depth + 1)
                   max_depth = max(max_depth, child_depth)
           return max_depth
   ```

   ```javascript
   // Apollo Server (Node.js) with graphql-depth-limit
   import depthLimit from 'graphql-depth-limit';
   import { createComplexityLimitRule } from 'graphql-validation-complexity';

   const server = new ApolloServer({
     schema,
     validationRules: [
       depthLimit(7),
       createComplexityLimitRule(1000, {
         onCost: (cost) => console.log('Query complexity:', cost),
       }),
     ],
   });
   ```

2. **Disable introspection in production**:

   ```javascript
   // Apollo Server
   const server = new ApolloServer({
     schema,
     introspection: process.env.NODE_ENV !== 'production',
   });
   ```

   ```python
   # Graphene — disable introspection via validation rule
   from graphql import build_ast_schema, validate
   from graphql.validation import NoSchemaIntrospectionCustomRule

   def execute_query(query_string, schema, context):
       document = parse(query_string)
       errors = validate(schema, document, rules=[NoSchemaIntrospectionCustomRule])
       if errors:
           return {"errors": [str(e) for e in errors]}
       # execute...
   ```

3. **Implement field-level authorization in resolvers**:

   ```python
   # BAD — authorization only at query level
   @login_required
   def resolve_user(root, info, id):
       return User.objects.get(id=id)  # returns all fields including email, SSN

   # GOOD — field-level checks
   class UserType(graphene.ObjectType):
       email = graphene.String()
       ssn_last4 = graphene.String()

       def resolve_email(root, info):
           # Only return email if viewer is the user or an admin
           viewer = info.context.user
           if viewer.id != root.id and not viewer.is_admin:
               return None
           return root.email

       def resolve_ssn_last4(root, info):
           viewer = info.context.user
           if viewer.id != root.id:
               raise PermissionError("Cannot access SSN of another user")
           return root.ssn_last4
   ```

4. **Rate limit by query complexity, not just request count**:

   ```python
   import time
   from collections import defaultdict

   class ComplexityRateLimiter:
       WINDOW_SECONDS = 60
       MAX_COMPLEXITY_PER_WINDOW = 5000

       def __init__(self):
           self._usage: dict = defaultdict(list)

       def check_and_record(self, user_id: str, complexity: int) -> bool:
           now = time.time()
           window_start = now - self.WINDOW_SECONDS

           # Remove expired entries
           self._usage[user_id] = [
               (ts, c) for ts, c in self._usage[user_id] if ts > window_start
           ]

           current_usage = sum(c for _, c in self._usage[user_id])
           if current_usage + complexity > self.MAX_COMPLEXITY_PER_WINDOW:
               return False

           self._usage[user_id].append((now, complexity))
           return True
   ```

5. **Prevent batching abuse — limit query batching**:

   ```javascript
   // Apollo Server — disable query batching or set a batch size limit
   const server = new ApolloServer({
     schema,
     allowBatchedHttpRequests: false,  // disable batching entirely
   });

   // Or limit batch size via middleware
   app.use('/graphql', (req, res, next) => {
     if (Array.isArray(req.body) && req.body.length > 10) {
       return res.status(400).json({ error: 'Batch size exceeds limit of 10' });
     }
     next();
   });
   ```

6. **Validate and sanitize all input arguments**:

   ```python
   import graphene
   from graphene import String, Int
   import re

   class SearchUsers(graphene.ObjectType):
       search_users = graphene.List(
           UserType,
           query=String(required=True),
           limit=Int(default_value=20)
       )

       def resolve_search_users(root, info, query, limit):
           # Validate inputs
           if len(query) > 100:
               raise ValueError("Search query too long")
           if limit > 100:
               raise ValueError("Limit exceeds maximum of 100")
           # Use ORM — never string interpolation into raw SQL
           return User.objects.filter(name__icontains=query)[:limit]
   ```

## Rules

- Disable introspection in production — if schema discovery is needed (for internal tools), require authentication.
- Every resolver that returns sensitive data must check authorization — not just the top-level query.
- Query aliases allow executing the same field multiple times; depth and complexity limits must account for aliases.
- Persisted queries (allowlist of approved query hashes) eliminate arbitrary query execution — consider for production APIs.

## Common Mistakes

- **`__typename` bypassing introspection blocks** — `__typename` is a meta-field that leaks type names even when introspection is disabled; consider whether it needs to be blocked too.
- **Using DataLoader only for performance** — DataLoader (batched loading) also mitigates N+1 query amplification attacks; skipping it allows a single query to cause thousands of DB round trips.
- **Missing authorization on mutation arguments** — checking `canUpdateUser(viewer, userId)` but not `canSetRole(viewer, newRole)` allows privilege escalation through mutation arguments.
- **Error messages exposing schema details** — default GraphQL error messages often include resolver and field names; customize error formatting to remove internal details.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…