MEDIUM adonisjsgraphql introspection abuse

Graphql Introspection Abuse in Adonisjs

How Graphql Introspection Abuse Manifests in Adonisjs

GraphQL introspection allows clients to query the schema for types, fields, and directives. While useful for development tools, exposing introspection in production can leak sensitive API structure, enabling attackers to map hidden endpoints, administrative mutations, or deprecated fields. In AdonisJS applications using @adonisjs/graphql-server, introspection is enabled by default in development but may remain active in production if not explicitly disabled.

A common misconfiguration occurs when developers copy development settings to production without adjusting the GraphQL server options. For example, in start/graphql.ts, the GraphQLServer instance might be initialized without setting introspection: false:

import { GraphQLServer } from '@adonisjs/graphql-server'

const graphqlServer = new GraphQLServer(
  schema,
  {
    // Missing introspection: false in production
    playground: false,
    // No introspection flag → defaults to true
  }
)

graphqlServer.start()

This exposes the full schema via introspection queries like:

{
  __schema {
    types {
      name
      fields { name }
    }
  }
}

Attackers can use this to discover:

  • Hidden mutations like deleteUser or updateRole
  • Deprecated fields that may lack proper authorization
  • Relationships that expose indirect access to sensitive data (e.g., User.creditCard)
  • Custom scalar types that might indicate unsafe handling (e.g., JSON scalars)

In AdonisJS, this risk is amplified when combined with misconfigured guards. For instance, if a mutation like updateUserRole relies on a guard that fails to check for admin privileges due to a typo (role === 'adim'), introspection reveals the mutation’s existence, allowing privilege escalation. CVE-2020-28477 demonstrated similar schema exposure leading to unauthorized data access in GraphQL endpoints.

Adonisjs-Specific Detection

Detecting GraphQL introspection exposure in AdonisJS requires checking both runtime behavior and configuration. middleBrick identifies this by sending an introspection query to the /graphql endpoint and analyzing the response for schema data. It does not require authentication, scanning the unauthenticated surface as part of its black-box assessment.

During a scan, middleBrick checks for:

  • Presence of __schema or __type fields in the response
  • Return of type definitions, field names, or directive information
  • Response times indicating reflective processing (not cached errors)

To manually test, use curl to send an introspection query:

curl -X POST https://api.example.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{ __schema { types { name } } }"}'

A successful response returns JSON with a data.__schema.types array. If the server returns an error like Introspection is disabled or Cannot query field '__schema', introspection is properly restricted.

middleBrick also correlates findings with AdonisJS-specific patterns. For example, if the endpoint is served via AdonisJS and the response includes headers like x-powered-by: AdonisJs, the scanner notes the framework context. It cross-references this with the GraphQL server configuration: if introspection is enabled in a non-development environment (detected via NODE_ENV hints in headers or error messages), it flags the issue under the "Data Exposure" category with medium severity.

Additionally, middleBrick checks for related risks: if introspection exposes a mutation like execSql (a known dangerous pattern in some legacy AdonisJS plugins), it elevates the severity due to potential SQL injection chains.

Adonisjs-Specific Remediation

Disabling introspection in AdonisJS is straightforward and should be done in production environments. The @adonisjs/graphql-server package accepts an introspection boolean in its constructor. Set this to false when NODE_ENV is not development.

Update start/graphql.ts to conditionally disable introspection:

import { GraphQLServer } from '@adonisjs/graphql-server'
import Env from '@ioc:Adonis/Core/Env'

const graphqlServer = new GraphQLServer(
  schema,
  {
    playground: Env.get('NODE_ENV') === 'development',
    introspection: Env.get('NODE_ENV') === 'development', // Disable in prod/staging
  }
)

graphqlServer.start()

This ensures introspection is only available when explicitly needed for development. For environments where you want to retain introspection for internal tools (e.g., staging), consider:

  • Using environment-specific schemas (e.g., schema.staging.ts with introspection enabled)
  • Deploying a separate GraphQL endpoint for tooling, protected by network policies or VPN
  • Using Apollo Studio or GraphQL Voyager behind authentication, not exposing the raw endpoint

If you must keep introspection enabled for legitimate reasons (e.g., public API documentation), implement additional controls:

  1. Rate limiting: Use AdonisJS’s built-in RateLimiter middleware to restrict introspection queries. Apply it selectively to the GraphQL route in start/routes.ts:
Route.group(() => {
  Route.post('/', 'GraphqlController.handle')
}).middleware(['auth', 'rateLimiter:10']) // 10 requests/minute
  1. Query depth limiting: Prevent expensive introspection queries by limiting query depth. While @adonisjs/graphql-server doesn’t include this by default, you can wrap the schema with graphql-depth-limit:
import { depthLimit } from 'graphql-depth-limit'

const schemaWithLimits = applyMiddleware(schema, depthLimit(5))

const graphqlServer = new GraphQLServer(schemaWithLimits, { introspection: true })

After remediation, verify the fix by attempting an introspection query. It should return an error such as:

{
  "errors": [
    {
      "message": "Introspection is disabled",
      "locations": [{ "line": 1, "column": 2 }],
      "path": ["__schema"]
    }
  ]
}

Regularly scan with middleBrick to ensure introspection remains disabled across deployments, especially after dependency updates or configuration changes.

Frequently Asked Questions

Does disabling GraphQL introspection in AdonisJS break GraphQL IDEs like Apollo Studio or GraphiQL?
Yes, disabling introspection prevents these tools from auto-loading the schema, as they rely on introspection to discover types and fields. However, you can still use them by manually providing the schema via a static .graphql file or exporting it from your AdonisJS server during development. In production, IDEs should not require access to the live endpoint; instead, use schema publishing tools (like Apollo GraphOS) to share the schema securely with stakeholders.
Can middleBrick detect if introspection is disabled but the GraphQL endpoint still leaks schema information through error messages?
middleBrick focuses on detecting successful introspection responses as a primary indicator of exposure. While it does not actively probe for schema leakage via error messages (e.g., detailed validation errors revealing type names), such findings would fall under the "Data Exposure" or "Input Validation" categories if they constitute a security issue. For example, if an error returns a stack trace containing schema details, middleBrick may flag it as improper error handling. However, the core check for introspection abuse remains the presence of __schema or __type in a successful response.