GraphQL Security
debt(d7/e5/b7/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints list semgrep, owasp-zap, and graphql-cop as tools — these are specialist/security-focused tools not in a default linter stack. Missing depth limits or exposed introspection won't be caught by a compiler or standard linter; they require deliberate security tooling or manual review. Slightly better than d9 because automated tools like graphql-cop can flag the patterns, but they must be explicitly configured and run.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix suggests disabling introspection, adding depth/complexity limits, and requiring authentication — these are not a single-line patch. Depth limiting and DataLoader integration touch schema setup, resolver configuration, and middleware layers. Authentication before query execution may require cross-cutting middleware changes. Not a full architectural rework, but spans multiple configuration and code points.
Closest to 'strong gravitational pull' (b7). GraphQL security concerns — depth limits, complexity budgets, DataLoader patterns, authentication enforcement — shape every resolver and schema decision across the API layer. Any new field, type, or query added must be evaluated against these constraints. The applies_to scope covers the entire web/api context, meaning every GraphQL feature addition carries this ongoing security tax.
Closest to 'serious trap (contradicts how a similar concept works elsewhere)' (t7). The misconception field directly states that developers assume 'GraphQL is inherently more secure than REST because it uses a schema.' This is a well-documented and serious trap: the schema describes possibilities but does not limit query aggressiveness. Developers familiar with REST or typed APIs naturally but incorrectly assume the schema acts as a security boundary, making this a t7-level contradiction with reasonable prior expectations.
Also Known As
TL;DR
Explanation
GraphQL security concerns: Introspection (reveals your entire schema to attackers — disable in production), query depth attacks (deeply nested query exhausts CPU/memory), query complexity attacks (one query requesting millions of related objects), N+1 amplification (no DataLoader → single query triggers thousands of DB calls), field suggestion attacks (error messages leak schema information), and injection in resolver arguments (same SQL/command injection risks as REST). Mitigations: depth limiting, complexity analysis, query cost calculation, and DataLoader for batching.
Common Misconception
Why It Matters
Common Mistakes
- Introspection enabled in production — allows attackers to enumerate your entire data model.
- No query depth limit — recursive queries exhaust memory and CPU.
- No DataLoader — every resolver fires individual DB queries, creating N+1 at scale.
- Returning detailed error messages in production — field suggestions and schema hints leak information.
Code Examples
// No limits — vulnerable to abuse:
$server = new GraphQL\Server\StandardServer([
'schema' => $schema,
// No depth limit, no complexity limit
// Introspection enabled (default)
// No DataLoader
]);
// Single query: user{orders{items{product{reviews{author{orders{...}}}}}}}
// Hardened GraphQL config:
use GraphQL\Validator\Rules\QueryDepth;
use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\Rules\DisableIntrospection;
$rules = GraphQL::getStandardValidationRules();
$rules[] = new QueryDepth(5); // Max 5 levels of nesting
$rules[] = new QueryComplexity(100); // Max complexity score of 100
$rules[] = new DisableIntrospection(); // Disable in production
// Always use DataLoader for resolvers:
'author' => fn($post) => $userLoader->load($post['author_id']),