Skip to content
Back to field notes
APIsJune 6, 20266 min read

GraphQL introspection enabled in production: a launch check

A GraphQL endpoint with introspection left on answers a single unauthenticated query with your entire schema, every type, field, and mutation. This note shows how to check yours before you share the URL.

GraphQLAPIsSchema exposure

An introspection query returns your whole schema to anyone

The concrete launch risk is this: a GraphQL endpoint with introspection enabled answers one unauthenticated query with the full description of your API. Send an introspection query to the public URL and the server returns every type, every field, every input argument, and every mutation name, including the ones your UI never calls. No login, no token, no rate limit needed for that one request. This is GraphQL introspection in production, and on a freshly scaffolded API it is usually on.

Be precise about what this is and is not. Introspection exposure is observable: you send a known query, you get the schema back, that is a fact you can reproduce in seconds. What it enables is the harder part, and it is only possible, not proven. A leaked schema does not by itself read your data or run your mutations. It is a map. It tells an outside reader the exact shape of every operation, the internal field names, the relationships between types, and the names of mutations like deleteUser or setRole that your front end carefully never shows. That map turns "guess what this API can do" into "read the list."

So the launch problem is not that introspection is an exploit. It is that you are publishing your internal API design to anyone who asks, before you have confirmed that the operations it describes are actually protected. Schema exposure makes every other gap easier to find.

  • Observable now: the endpoint returns a complete schema to an unauthenticated introspection query.
  • Only possible: that someone uses the revealed mutation and field names to probe for missing authorization.
  • The schema is information disclosure (an OWASP API security and CWE information-exposure concern), not authentication or authorization on its own.

Why AI-built GraphQL APIs ship with it on

Most GraphQL server libraries and hosted layers enable introspection by default, because it is what makes the developer tooling work. GraphiQL, the in-browser explorer, code generators, and IDE autocomplete all read the schema through introspection. During development that default is helpful, so the frameworks ship it on and leave turning it off as a deployment decision.

That default collides with how AI tools scaffold an API. A prompt like "give me a GraphQL API for posts and comments" produces a working server, a working explorer, and a working front end fast. The explorer is part of why it feels finished: you open the playground, the schema is right there, queries run. Nothing in that loop forces a separate step where someone decides the production endpoint should stop describing itself to the public.

  • The schema being visible is a feature in dev, so its presence in prod looks normal, not like a leak.
  • Fast deploy means the public URL is live before anyone reviews which capabilities the schema advertises.
  • The same shortcut often leaves the playground UI mounted in production too, which is a second, louder version of the same disclosure.

Check your own endpoint from the outside

You can confirm this on your own app with no attack and no third-party target. You are sending the same query the developer tooling sends.

First, find the endpoint. In your browser open DevTools, go to the Network tab, and use the app normally. GraphQL traffic usually shows as POST requests to a single path, often /graphql or /api/graphql, with a JSON body containing a query field. That path is your endpoint.

Then send an introspection query to it. The standard probe is a query named IntrospectionQuery that asks for __schema, which lists every type and field. The minimal version requests __schema with its types and their names; the full version is what GraphiQL sends. Post it to your own endpoint with no auth header and read the response.

Repeat against your real deployed URL, not localhost. The dev server and the production build can differ, and only the deployed answer matters before you share the link.

  • Suspicious result: the response body contains a __schema object full of your types, fields, and mutation names. That means introspection is open to anonymous callers.
  • Safer result: the server returns an error such as introspection being disabled, or a generic validation error, and no schema payload. That is the expected production behavior.
  • Also check the path directly in a browser. If a GraphiQL or Apollo playground UI loads at the endpoint for an anonymous visitor, that is the same disclosure with a friendlier interface, and it should be off in production too.

Turn introspection off, and know what that does not fix

The direct fix is to disable introspection in production. Every major GraphQL server has a switch for it. In Apollo Server you stop sending the introspection schema in production builds; in other servers you set an introspection flag to false or add a validation rule that rejects queries touching __schema and __type. Gate it on environment so your dev tooling still works locally and the public deployment stays quiet. Unmount any playground or explorer UI from the production endpoint at the same time.

Be honest about the boundary of that fix. Disabling introspection hides the map. It does not protect the territory. This is the part that matters most.

  • A determined caller can still discover fields and operations by guessing common names, by reading your client bundle (your front-end queries are shipped to the browser), or from old schema dumps. Introspection off raises effort, it does not create access control.
  • The real defense is authorization on every resolver: each query and mutation must check whether the current caller is allowed to run it, regardless of whether they knew its name. Hiding deleteUser is worthless if deleteUser runs for any caller who sends it.
  • Disabling introspection also does nothing about query cost. An open endpoint can still be hit with deep, expensive, or batched queries, so depth limits, complexity limits, and rate limiting are separate controls you still need.

Turning introspection off is a fast, correct hardening step, but it is concealment, not protection. Distinguish the two: authentication proves who the caller is, authorization decides what that caller may do, and introspection control only decides whether the schema is handed out. Treat a hidden schema as defense in depth on top of resolver-level authorization, never as a replacement for it.

Where VibeCodeGuard fits, and what it does not prove

VibeCodeGuard's Launch Check looks at this from the public surface, the same way an outside visitor would. It can send an introspection query to your public endpoint and flag a successful full-schema response as a launch blocker, with severity and a fix direction. It checks the thing anyone on the internet can actually reach: does the endpoint describe itself to an unauthenticated request. That is a precise, reproducible signal, and it is the right thing to catch before the URL spreads.

What it does not do is read your resolver code or confirm your authorization rules. A clean introspection result means the schema is no longer broadcast; it says nothing about whether each mutation checks permissions. That part needs code review and manual testing of individual operations, and the Launch Check does not replace it.

Next step: send one introspection query to your deployed endpoint right now and confirm whether the schema comes back. If it does, gate introspection off for production and unmount the playground, then put authorization checks on your resolvers. Run a focused Launch Check against the public URL before sharing it, so an open schema shows up in your scan instead of someone else's.

A passing introspection check is one signal, not proof your GraphQL API is safe. The scan sees what is reachable from outside, not your resolver logic, so a closed schema sitting in front of unauthorized mutations would still pass. Disabling introspection, then auditing authorization on each operation, are two separate jobs and you need both.

> launch check

Scan the public surface before launch.

Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.