Safeguard
Security Guides•

How do you security-test a GraphQL API? Six checks in order

Start with introspection, then field suggestions, batching, query cost, authorization and injection. A short order of work for testing a GraphQL API.

Safeguard Security Team
7 min read

Test a GraphQL API in six steps: introspection, field suggestions, batching, denial of service through deep queries, authorization, and injection. Do them in that order, because each early step reveals the schema or the limits that make the later steps more precise. Use a test environment and test accounts.

Why GraphQL needs a different routine

A REST scan walks a list of paths. A GraphQL API usually has one path, often a single POST endpoint, and the real surface is the schema behind it. Per-endpoint checks can look clean while the schema exposes a lot. The OWASP Web Security Testing Guide has a dedicated GraphQL section for this reason.

The six checks

StepWhat you doWhat you are looking for
1. IntrospectionSend an introspection query and map the schemaFull schema in a production-facing environment, plus sensitive operations such as queries that return tokens
2. Field suggestionsSend a misspelled field nameError text that suggests real field names even when introspection is off
3. BatchingSend several operations in one requestPer-request rate limits or WAF rules that count the batch as one
4. Denial of serviceSend deeply nested or very large queriesNo limit on depth, complexity or execution time
5. AuthorizationRun the same operations as different roles and as an unauthenticated callerSensitive queries and mutations reachable by the wrong user, objects readable by non-owners
6. InjectionTest arguments and custom scalarsSQL, command or script injection through values passed to backends

These steps follow the WSTG GraphQL page. It adds two extra checks worth keeping: retry a blocked operation under an alias to see whether a name-based denylist still runs the resolver, and review error messages for internal detail.

Working notes for each step

Introspection. If it works, export the schema and keep it. You will use it to list every query, mutation and type. OWASP's cheat sheet advises disabling introspection in production or public environments, and also notes that this does not fully stop field discovery.

Field suggestions. The cheat sheet suggests turning off "Did you mean" suggestions when introspection is off. If your test shows them, that is a concrete fix.

Batching. Test whether login or one-time password checks can be repeated inside one request. The cheat sheet says to prevent batching for sensitive operations and to cap the number of operations that can be batched.

Denial of service. Increase nesting in small steps on a staging API. The cheat sheet itself suggests testing whether a staging API can be slowed by a nasty query before investing in cost analysis. Do not do this against production.

Authorization. GraphQL does not enforce permissions by default. Check object access on nodes as well as edges. The cheat sheet cites a bug where edges were checked but nodes were not, and says to check for node or nodes fields that fetch objects by ID.

Injection. Resolvers pass values to databases and other services, so apply your normal injection tests to arguments.

Record the results as a table

Keep one row per operation and one column per role, with the status returned. Gaps in the pattern, for example an admin mutation that a normal user can run, show up immediately.

Worked example: a small shop schema

Take a shop API with one GraphQL endpoint. The schema has a products query, an order query that takes an ID, a me query, an updateProfile mutation, a cancelOrder mutation, and an internal allUsers query meant for admins. Walk through the six checks in order.

1. Introspection. The tester sends an introspection query as an anonymous caller. The full schema comes back, including allUsers and a field called apiToken on the user type. Two items to follow up: introspection is open, and a sensitive field exists. The tester saves the schema file, because every later step uses it.

2. Field suggestions. The team turns introspection off in staging and retests. The tester then asks for a field named apiTokn. The error says, "Did you mean apiToken?" Disabling introspection hid the schema document, but the error text still leaks the field name. The fix is to turn off suggestions as well.

3. Batching. The tester sends one request containing fifty login mutations with different passwords. Rate limiting counts HTTP requests, so the batch counts as one. This is the bypass the cheat sheet warns about. The fix is to forbid batching on login and to count operations, not requests.

4. Denial of service. The schema has cycles: a user has orders, an order has items, an item has a product, a product has reviews, and a review has an author who is a user again. The tester nests the query six levels deep on staging, then eight, then ten, watching response time. At ten levels the response takes several seconds and CPU rises. There is no depth limit. The fix is a depth limit and a timeout, with amount limits on list fields.

5. Authorization. Using the schema, the tester builds a table of operations and callers:

OperationAnonymousCustomerAdmin
productsAllowedAllowedAllowed
meDeniedAllowedAllowed
order(id) for own orderDeniedAllowedAllowed
order(id) for another customer's orderDeniedShould be deniedAllowed
allUsersDeniedShould be deniedAllowed
cancelOrder for another's orderDeniedShould be deniedAllowed

The tester runs each as each caller. The customer account gets a 200 on allUsers, so a function level flaw exists. The order query for another customer's ID also returns data, so there is an object level flaw too.

6. Injection. The products query takes a search argument. The tester sends a single quote and typical injection strings. One returns a database error in the response, which suggests the argument reaches a query unsafely.

What the findings add up to

Six steps, seven findings: open introspection, a leaking suggestion, batch bypass of rate limits, no depth limit, a function level flaw, an object level flaw, and a probable injection. None needed advanced tooling. They needed the schema, three accounts, and an ordered routine. That is why the order matters: introspection gives the schema, the schema gives the operations table, and the table drives the authorization and injection checks.

Turning it into a routine

Keep the operations table in the repository. When a developer adds a query or mutation, the pull request adds a row with the expected access, and a test that checks it. That keeps step 5 from becoming a one-off exercise.

Try it yourself

Open the demo to see how an example API scan presents endpoints and findings. For a GraphQL API, imagine the rows being operations in a schema rather than URLs.

Click through it at your own pace, or open it full screen.

Quick check

If introspection is disabled, is the schema private?

Not necessarily. Field suggestions in error messages can reveal real names, and attackers can try guesses. Test for both.

Does an OpenAPI import work for GraphQL?

No. GraphQL needs its own schema as input, usually through introspection or a schema file.

Is GraphQL less secure than REST?

Not by design. It moves the risk: one endpoint, flexible queries, and no built-in permissions, so authorization, batching and cost limits need deliberate work.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.