← Back to the GraphQL Introspection Exposure Checker
Pasting the standard __schema introspection query into GraphiQL, Postman, or a
curl command and reading whether it comes back populated is a real, fast way to
answer the yes/no question: does this endpoint answer an unauthenticated introspection query at
all. It is also the method most developers already reach for, since every GraphQL server ships
a playground that runs the identical query. This page names no product or company being
compared against -- "checking by hand" is a generic manual habit, not a competing tool.
| Case | Checking by hand | This tool |
|---|---|---|
| Endpoint answers a plain introspection query with a populated schema | Correctly reads as "enabled" | Correctly reads as "enabled" |
| Endpoint returns an explicit "introspection is disabled" error | Correctly reads as "disabled" | Correctly reads as "disabled", and names the distinction from a dead or wrong-URL endpoint |
| Testing from a GraphiQL UI hosted on the API's own domain | Same-origin request always succeeds -- says nothing about whether a third-party page could read the same response | Runs the request from this page's own origin, so a successful browser result demonstrates the target's CORS policy actually permits a cross-origin read |
| Schema exposes a mutation type buried among hundreds of query fields | Easy to skim the default Query tab and never open Mutation | Always parses mutationType.fields and subscriptionType.fields specifically and escalates the finding to danger the moment any mutation field exists |
| Field names matching sensitive wording (admin, resetPassword, impersonateUser) | Requires reading every field name in a schema that can run to thousands of lines | Scans every query and mutation field name against a fixed sensitive-wording list and lists every match |
| Repeatable in CI on every deploy | Manual, point-in-time | CLI returns a real exit code from the same classification |
A GraphQL playground served by the API itself runs its introspection query from that API's own
origin, and a same-origin request is never subject to CORS -- so success there proves only that
the endpoint answers introspection queries at all, not that an attacker's page on a different
origin could read the response too. This tool's browser check deliberately runs from a separate
origin (this page), so a successful result means the target's own CORS configuration actually
permits an arbitrary third-party site to read a schema dump from a visitor's browser -- the
realistic exposure path, and one that testing from the API's own tooling cannot demonstrate
either way. When the target's CORS policy blocks that read, the browser check correctly reports
it cannot tell and the CLI -- never subject to CORS, since that restriction is enforced by
browsers, not by Node's fetch -- still gets a definitive answer.
"Introspection is enabled" is one finding. Whether the exposed schema includes a mutation type --
the API's entire write surface, before an attacker has to guess a single field name -- is a
materially different severity, and it is the one thing a person skimming a large schema by eye
is most likely to miss, because GraphiQL's docs explorer opens on the Query type by default and
Mutation is a separate branch to think to click into. This tool raises the finding to danger the
moment mutationType.fields is non-empty, specifically so that severity does not
depend on a reader remembering to open the second tab.
If you already know the endpoint answers introspection and you only want to browse the schema interactively -- read a specific type's fields, follow a resolver's arguments -- GraphiQL's docs explorer is the better tool for that; it is built for exploration, not for a pass/fail exposure verdict. The gap this tool closes is the two questions above: whether the exposure is reachable from an arbitrary origin, and whether the exposed surface includes writes, both of which a same-origin skim of the schema does not answer.
It answers the yes/no question directly: send the standard __schema introspection query, and a populated response means introspection is enabled. That single request genuinely settles whether the endpoint answers at all -- the same request this tool sends.
A GraphiQL or Playground UI served by the API itself makes its introspection request from that API's own origin, which any same-origin request is always allowed to do -- it says nothing about whether an arbitrary third-party page, running in a visitor's browser on a different origin, could run the same query and read the response. That is exactly what CORS governs, and it is the realistic exposure: a malicious or compromised page anywhere on the web probing the endpoint from a stranger's browser. This tool's browser check runs from this page's own origin and reports whether the target's CORS policy actually permits that cross-origin read, then falls back to a CLI (which is never subject to CORS) when it does not -- so a clean result names which of the two questions it answered rather than leaving CORS untested.
A real API's introspection response can list hundreds of types and enough fields that the raw JSON runs to thousands of lines. GraphiQL's docs explorer defaults to browsing the Query type, and mutationType is a separate branch of the same tree a reader has to think to open. This tool always parses mutationType.fields and subscriptionType.fields specifically, counts them, and raises the finding as danger rather than notice the moment any mutation field is present -- because an exposed write surface is a materially worse finding than an exposed read surface, and that distinction should not depend on a reader remembering to check the second tab.
Reading every field name in a schema of even moderate size for words like admin, resetPassword, or impersonateUser is tedious and easy to shortcut once the obvious top-level fields look ordinary. This tool scans every query and mutation field name against a fixed list of sensitive-operation wording and lists the matches -- still a hint to review by hand, not proof of a missing authorization check, since GraphQL field-level auth is common and no introspection response can show it. What the automated scan changes is coverage: it checks every field name, not just the ones a skim happens to land on.