Give the scanner the GraphQL schema or endpoint so it can generate valid queries, supply working credentials, and cap how much load it sends. Then add manual checks for authorization, because a generic scan cannot know who should see which object.
Why GraphQL needs different handling
A REST API exposes many URLs. A GraphQL API usually exposes one, and the real work happens inside the query text. A crawler that follows links finds a single endpoint and little else. Without a schema, the scanner has no idea which queries, fields and arguments exist.
What scanners need
The ZAP GraphQL add-on documentation says it lets you import GraphQL definitions and send queries generated from them, and that it can detect GraphQL definitions automatically and spider them as long as they are in scope. It also lets you limit the number of messages imported, and import from an endpoint or a schema file. Other tools offer similar import steps. The point is the same everywhere: feed the scanner the schema.
Steps
- Get the schema. Export it from your build, or use introspection on a test environment.
- Import it into the scanner and confirm queries and mutations appear in the scan tree.
- Add authentication. Most useful fields sit behind a token. Use a dedicated low-privilege test account.
- Set limits. Cap query depth and the number of imported messages for the first run, then raise them.
- Scope it. Point the scan at the test host only.
- Run and review. Check which operations were actually exercised, not just the count of alerts.
- Repeat with a second account to look for access differences, below.
Checks that a generic scan will not complete
The OWASP GraphQL Cheat Sheet lists the issues worth testing. Among them are insecure default configurations such as excessive errors, introspection and GraphiQL, broken authorization including insecure direct object references, and batching attacks, a GraphQL-specific form of brute force. It also notes that depth and amount can be unlimited by default, which can lead to denial of service, and recommends limits, pagination and timeouts.
| Check | How to test it |
|---|---|
| Introspection in production | Send an introspection query to the production-like environment |
| Verbose errors | Send a malformed query and read the error text |
| Depth and amount limits | Send a deeply nested query in a test environment and watch the response |
| Authorization | Request an object ID that belongs to another test account |
| Batching | Send many operations in a single request and see if rate limits count them |
Authorization is the hard one. The cheat sheet says to always validate that the requester is authorized to view or modify the data they ask for. A scanner using one account cannot tell that a second account's invoice should be off limits. Use two accounts and compare.
Protect the test environment
Query cost can be high, so go gently. Test deep queries and big batches on staging, not production, and agree a time window with the owners. Set a sensible timeout so a runaway query does not tie up the service.
What to record
Keep the schema version, the scan date, the accounts used and the operations covered. Without that, a clean report says very little.
A worked example
A team has a GraphQL service for a customer portal. It exposes one endpoint, with queries for customers, invoices and tickets, and mutations to update profile data. They follow these steps on staging.
- Export the schema from the build pipeline and store it with the scan job, so the scan always matches the deployed version.
- Create two test users, Alice and Bob, each with a few invoices.
- Import the schema into the scanner and confirm that 14 queries and 9 mutations appear in the tree.
- Run the scan as Alice with a login token, using a low depth limit and a message cap for the first pass.
- Review coverage. The scanner exercised 12 of 14 queries and 5 of 9 mutations. Four mutations needed fields the scanner could not guess, such as a valid ticket ID.
- Seed those IDs as sample values and rerun. Coverage rises.
- Run the manual access check. As Alice, request Bob's invoice by ID. The response shows Bob's data. This is a broken authorization finding that no scanner flagged.
The counts here are illustrative, not from a real system. The lesson is real: coverage reports expose gaps, and authorization needs a deliberate two-account test.
A table of protections to verify
The OWASP GraphQL Cheat Sheet lists several controls. Use it as the basis for a checklist.
| Control | Test | Pass looks like |
|---|---|---|
| Introspection and GraphiQL disabled outside development | Send an introspection query to the production-like environment | An error or empty result |
| Error messages kept short | Send a malformed query | A generic message, no stack trace |
| Depth limit | Send a deeply nested query in test | Rejected with a clear error |
| Amount limit and pagination | Ask for a very large number of objects | Capped result |
| Timeouts | Send an expensive query in test | Stopped within the limit |
| Batching limits | Send many operations in one request | Counted against rate limits |
| Authorization on every resolver | Request another user's object | Denied |
Aliases and batching
GraphQL lets a client run the same operation many times in one request. A rate limit that counts HTTP requests misses this. The cheat sheet advises applying rate limits in the business logic layer together with query cost limits and batching limits. Test it by sending one request with many aliased login attempts and checking whether lockout still triggers.
Keep results useful
Report findings by operation name, not just by URL, since the URL is always the same. Include the query text that triggered the finding, with test data only. A developer can then paste it into a client and reproduce it.
Production and staging
Introspection is useful in development and often disabled in production. If your scanner relies on it, import the schema from a file instead and keep production locked down. Run the heavy depth and batching tests only on staging.
Where Safeguard fits
GraphQL services are built from open source libraries too. Safeguard offers SBOM and dependency scanning with reachability analysis for those packages, and a free scan at gold.safeguard.sh/scan.
Try it yourself
Click through it at your own pace, or open it full screen.
Look at how an API target is described to the scanner and what the results say about coverage.
Quick check
Can a crawler find GraphQL queries on its own?
Rarely. Import the schema so the scanner can build valid queries.
Does a clean DAST report mean authorization is sound?
No. Test with two accounts to check that one cannot read the other's data.
Should introspection stay enabled in production?
The OWASP cheat sheet lists introspection among insecure defaults to disable. Import the schema from a file for scanning instead of relying on it live.
Sources
Last reviewed: 19 October 2026.