Fix guide · medium · graphql_playground_exposed
GraphQL Playground / GraphiQL UI exposed in production
A GraphQL Playground, GraphiQL, or Apollo Sandbox UI is reachable on a production endpoint. The UI is a full interactive query editor against your live schema — useful for development, dangerous to ship.
Why it matters
GraphQL Playground (and its successors GraphiQL, Apollo Sandbox, and Apollo Studio Explorer) is the in-browser query editor that ships with most GraphQL servers. Hit /graphql in a browser and you get a fully-featured IDE with:
- A schema explorer that documents every type, query, mutation, and subscription.
- A query editor with autocomplete based on the introspection schema.
- A history pane that lets you save and replay queries.
- A documentation pane that shows description strings, deprecation reasons, and (in some configs) admin-only fields.
In development this is invaluable. In production it's a free reconnaissance tool for attackers. Even if you've disabled introspection, the Playground UI itself loads from a CDN and presents your endpoint URL prominently, with examples — meaning even hostile inspection of your traffic doesn't have to discover the endpoint, you've put it on a sign.
Common offenders shipping the Playground to production:
- Apollo Server v3+ with default config (the v4 default is to disable it in production; v3 had to be explicitly disabled).
- Yoga GraphQL (
graphiql: trueleft on). - Hasura with the default
HASURA_GRAPHQL_ENABLE_CONSOLEset. - Express-GraphQL with
graphiql: true. - AWS AppSync default test console URL exposed.
Disabling the Playground does NOT disable introspection — that's a separate flag (see /fix/graphql_introspection_enabled). Best practice is to disable both in production.
How to fix it
Apollo Server v3:
const server = new ApolloServer({
typeDefs, resolvers,
introspection: false,
plugins: [
process.env.NODE_ENV === 'production'
? ApolloServerPluginLandingPageDisabled()
: ApolloServerPluginLandingPageLocalDefault(),
],
});
Apollo Server v4:
const server = new ApolloServer({
typeDefs, resolvers,
introspection: process.env.NODE_ENV !== 'production',
});
// Landing page is disabled in production by default in v4.
GraphQL Yoga:
import { createYoga } from 'graphql-yoga';
const yoga = createYoga({
schema,
graphiql: process.env.NODE_ENV !== 'production',
});
Express-GraphQL:
app.use('/graphql', graphqlHTTP({
schema,
graphiql: process.env.NODE_ENV !== 'production',
}));
Hasura:
HASURA_GRAPHQL_ENABLE_CONSOLE: 'false'
Defense in depth: even with the Playground disabled, also disable introspection (see /fix/graphql_introspection_enabled) and ensure every resolver enforces its own auth. Schema-hiding is reconnaissance defence, not authorisation defence.
Did vibecheck flag this on your app?
If you reached this page from a vibecheck inspection report, the redacted match in your scan output is the exact string we found in your bundle. After applying the fix above, run the inspection again — the finding should clear.
Run another inspection