GraphQL Security Testing: Find BOLA, Introspection, and DoS Bugs
GraphQL puts many operations behind one endpoint, but it does not remove the need for authorization on every object, field, and action. The highest-value findings usually come from understanding the schema and then testing identity boundaries with two accounts.
Find and fingerprint the endpoint
Common paths include /graphql, /api/graphql, and /v1/graphql. A request containing {__typename} can confirm GraphQL without downloading data. Observe whether the server accepts GET, JSON POST, form-encoded requests, batches, or persisted-query hashes.
If introspection is enabled, use it only within scope and save the schema. If it is disabled, error suggestions, front-end bundles, mobile clients, and normal application traffic can still reveal operations.
Build an authorization matrix
Create two normal users and, when allowed, accounts with different roles. For each query and mutation, record:
- object identifiers accepted
- fields returned
- owning user or tenant
- role required
- whether authorization is enforced at the resolver
Swap one identifier at a time. Test both top-level objects and nested fields. A parent resolver may be protected while a child resolver leaks email, billing, notes, or internal status.
Aliases and fragments can expose inconsistent checks. They should not bypass limits or permissions, but they are useful for calling the same resolver with several controlled IDs in one request.
Test mutations separately
Read permission does not imply write permission. Replay mutations with another test user's object ID, remove optional ownership fields, and try server-managed fields such as role, status, or ownerId. Verify the state through the user interface or a follow-up query.
Avoid destructive tests. Use reversible changes on your own records.
Check batching and rate limits
GraphQL can pack work into aliases, arrays, or batched requests. A rate limiter that counts HTTP requests instead of resolver cost may allow password attempts, coupon checks, username enumeration, or expensive database work at scale.
Prove the weakness with a small number of operations. Do not run an actual denial-of-service test. Report the accepted query depth, alias count, response time, and missing cost control.
Test CSRF conditions
A GraphQL endpoint may be vulnerable to CSRF if it accepts mutations through GET or simple form content types while relying on cookies. Confirm whether JSON content type is enforced and whether anti-CSRF tokens or SameSite cookie controls protect state-changing operations.
Introspection is not the root cause
Production introspection increases discoverability and may reveal private types, but authorization must remain correct even when an attacker knows the full schema. Report introspection as supporting evidence unless it discloses genuinely sensitive internals.
A useful report
Include the operation, variables, two test identities, expected authorization rule, actual response, and impact. For BOLA, show that account A can access a specific object owned by account B. For complexity flaws, use safe measurements rather than traffic volume.
Defensive checklist
- Enforce authorization in every resolver and service method.
- Apply field-level controls to sensitive data.
- Use opaque identifiers only as defense in depth.
- Limit depth, aliases, batch size, and calculated query cost.
- Rate-limit by identity and operation cost.
- Allow state changes only through protected JSON POST requests.
- Disable production introspection when it is not required.
- Return generic errors without stack traces or schema secrets.
The best GraphQL testing is methodical: schema, identities, object boundaries, then resource limits.
Original Bugflare guide informed by PortSwigger's GraphQL security documentation.