GraphQL Security
SkillSecurityBound and control a GraphQL endpoint: operation cost budgets, cost-based rate limiting, pre-registered operations, alias and batch abuse on authentication paths, introspection, cache keying, and untyped scalar inputs. Use when generating schemas, resolvers, or server config, wiring limits or persisted operations, or reviewing a public /graphql endpoint.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the GraphQL Security skill
What this skill tells your AI
The instructions your AI receives, as published by shieldnet-360/secure-vibe in skills/graphql-security/SKILL.md and read by ahel’s review.
Rules (for AI agents)
ALWAYS
- Enforce a cost budget on every operation: a maximum depth, plus a complexity
score that weights list fields by the page size they can request. Derive the numbers
from your own schema and measure them — a depth of 7 is generous for a flat schema
and far too permissive for one with a many-to-many edge, where five levels of
nesting can address billions of nodes.
references/graphql-limits.mdcovers how to derive them and where each server configures them. - Rate-limit by cost consumed, not by request count. Every operation arrives at
the same URL, so the per-route limit
api-securityspecifies cannot tell a trivial query from one costing a thousand times more. Charge the complexity score against the caller's budget. - For a public or high-traffic API, restrict it to pre-registered operations — a build-time allowlist of the documents the client bundle actually contains, keyed by hash. Automatic Persisted Queries are not this: APQ lets a client register any operation it likes and then send its hash, which is a bandwidth optimization. Turning on APQ and believing the API is now allowlisted is a common and expensive mistake.
- Cap aliases per operation and operations per batch, and apply that counting to
authentication and other rate-limited paths. One request carrying a hundred
aliased
loginfields is a hundred attempts against any counter that increments once per request — this is the mechanism behind the account-takeover-by-batching incidents, not a bandwidth concern. - Batch resolver fetches with a DataLoader or a join. An N+1 resolver turns one cheap-looking query into a database round trip per parent row — the same amplification the cost limit exists to bound, except it happens below the cost analysis where that limit cannot see it.
- Key any response cache on the operation hash, the variables, and the caller's
authorization claims.
POST /graphqlis one URL for every request, so a cache keyed on the URL serves one tenant's data to another. - Validate the contents of
JSON, custom scalar, and loosely-typedinputarguments inside the resolver. The schema's type checking is what makes GraphQL inputs feel safe, and aJSONscalar opts straight out of it: whatever arrives is handed through unexamined. - Disable introspection and the in-browser playground (GraphiQL, Apollo Sandbox) on production endpoints — and understand what that buys. It raises the cost of reconnaissance and nothing more: field suggestions in error messages still leak names, tools reconstruct schemas from them, and every field stays callable whether or not it can be listed. Schema obscurity is never the authorization.
- Consult
auth-securityfor authorization, and run it per field rather than at the endpoint. One HTTP response aggregates many resolvers, so a single unguarded sensitive field leaks through any query that reaches it. - Consult
error-handling-securityfor what an error may carry. Theerrorsarray is GraphQL's public contract and field-level validation detail belongs in it; what must not appear there is the stack trace, the SQL, or resolver internals. Flattening a validation failure to a bareINTERNAL_SERVER_ERRORis hostile to callers without being safer. - Consult
file-upload-securityfor GraphQL multipart uploads. Arriving over GraphQL changes nothing about the rules there — quarantine, type validation, expansion limits, and scanning policy all still apply.
NEVER
- Trust a query's shape because "our clients only send well-formed queries". Anyone
can post to
/graphql. - Let
@skip/@includedecide whether an authorization check runs. They are execution directives evaluated before resolvers, which makes them a probe channel — an attacker toggles a field and reads the difference in the response — not a place to put a security decision. - Rely on a hand-rolled executor to reject a cyclic fragment. The specification
requires the
NoFragmentCyclesvalidation rule and conforming servers enforce it during validation; an executor that skips the validation phase does not.
KNOWN FALSE POSITIVES
- Pre-registered operations are already bounded, so depth and complexity checks on them are redundant. Keep the checks for anything arriving outside the allowlist.
- A public, read-only data API may run deliberately high cost limits with aggressive CDN caching. That is a documented per-endpoint trade-off, not an oversight.
- Introspection left on for an internal developer tool is acceptable only where the endpoint's authorization would be acceptable with introspection both on and off. Network placement — a VPN, a private subnet — is not the justification; reachability is not authentication.
Context (for humans)
GraphQL is not more powerful than a REST API in the computational sense. The query language has no loops, no recursion — the specification's validation rules forbid fragment cycles — and no arbitrary branching. What it has is amplification: one request can address an enormous amount of work, and one URL carries all of it.
That combination is what defeats controls built for REST. A per-route rate limit sees one route. A URL-keyed cache sees one key. A request counter sees one request, even when it carries a hundred aliased login attempts. Each of those controls is doing exactly what it was designed to do, against a shape it was not designed for — which is why the GraphQL-specific answer is almost always to move the unit of accounting from the request to the cost of the operation.
The second recurring failure is a control that sounds stronger than it is.
Introspection off is reconnaissance cost, not access control. Automatic Persisted
Queries are a bandwidth feature, not an allowlist. A schema type is a parser
guarantee, not validation, once a JSON scalar is in play.
References
references/verifying-findings.md— confirm or refute a finding, then lock itreferences/graphql-limits.md— deriving depth and complexity from your schema, where each server configures limits, and APQ versus trusted documentsrules/graphql_safe_config.json- OWASP GraphQL Cheat Sheet.
- GraphQL specification — validation.
- CWE-400.
- graphql-armor.
Signals
- GitHub stars
- 22
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
graphql-security- Source
- github.com/shieldnet-360/secure-vibe