MEDIUM graphql introspectionaxum

Graphql Introspection in Axum

How Graphql Introspection Manifests in Axum

Graphql introspection in Axum applications typically manifests through the async_graphql integration, where the introspection endpoint is enabled by default. When using Axum with async_graphql, the introspection query allows attackers to retrieve the complete schema of your GraphQL API, including all types, queries, mutations, and subscriptions. This information disclosure can be particularly dangerous in production environments where the schema reveals business logic, data structures, and potential attack vectors.

In Axum, introspection manifests through the async_graphql::Schema struct, which by default enables introspection queries. When you create a schema using Schema::new(queries, mutations), the introspection system is automatically included. This means that any GraphQL endpoint built with async_graphql in Axum will respond to introspection queries unless explicitly disabled.

The vulnerability becomes apparent when attackers send POST requests with introspection queries to your Axum GraphQL endpoint. For example, an attacker can retrieve the entire schema using a query like:

{
__schema {
types {
name
kind
description
}
}
}

In Axum applications, this manifests as an endpoint that responds with detailed schema information, potentially exposing sensitive field names, argument types, and even documentation that could aid in crafting more sophisticated attacks. The introspection data often includes internal field names, deprecated fields, and type relationships that shouldn't be public knowledge.

Another manifestation occurs when using Axum's middleware pattern with GraphQL. If you're using async_graphql_axum::graphql to mount your GraphQL handler, the introspection endpoint is accessible at the same path as your main GraphQL operations. This means that without proper configuration, any client can query your entire API structure.

Axum-Specific Detection

Detecting GraphQL introspection vulnerabilities in Axum applications requires examining both the code structure and runtime behavior. In your Axum codebase, look for schema creation patterns where introspection is enabled by default. The most common pattern is the use of async_graphql::Schema::new() without the SchemaBuilder configuration to disable introspection.

To detect this programmatically in your Axum application, you can examine how your GraphQL schema is constructed. Here's an example of vulnerable code:

use async_graphql::{Schema, EmptyMutation, EmptySubscription};
use async_graphql_axum::graphql;
use axum::{routing::post, Router};

async fn graphql_handler(schema: Schema<QueryRoot, EmptyMutation, EmptySubscription>) -> impl axum::response::IntoResponse {
graphql(schema)
}

let app = Router::new()
.route("/graphql", post(graphql_handler))
.with_state(schema.clone());

The detection pattern here is the use of Schema::new() without any introspection configuration. A secure implementation would use SchemaBuilder to explicitly control introspection behavior.

Using middleBrick to scan your Axum GraphQL endpoint provides automated detection. middleBrick's GraphQL introspection check sends actual introspection queries to your endpoint and analyzes the response. The scanner tests for:

  • Whether the endpoint responds to introspection queries
  • The completeness of schema information returned
  • Whether sensitive field names or deprecated elements are exposed
  • If the endpoint implements proper authentication for schema access

middleBrick's scanning process takes 5-15 seconds and requires no credentials or configuration. Simply provide your Axum GraphQL endpoint URL, and middleBrick will test the unauthenticated attack surface, including GraphQL-specific vulnerabilities like introspection exposure.

For runtime detection, you can add middleware to your Axum application that logs or blocks introspection queries. Here's an example middleware that detects introspection attempts:

use async_graphql::{Request, Response};
use axum::middleware::Next;
use axum::response::IntoResponse;

async fn introspection_detection(mut req: axum::http::Request, next: Next) -> impl IntoResponse {
let body = axum::body::to_bytes(req.body_mut()).await.unwrap();
let query_str = String::from_utf8(body.to_vec()).unwrap_or_default();

if query_str.contains("__schema") || query_str.contains("__type") {
// Log or block introspection attempt
return axum::response::Response::builder()
.status(403)
.body(axum::body::Full::from("Introspection disabled"))
.unwrap();
}

let mut new_req = axum::http::Request::from_parts(req.into_parts().0, body);
next.call(new_req).await
}

This middleware can be added to your Axum router to detect and block introspection queries before they reach your GraphQL handler.

Axum-Specific Remediation

Remediating GraphQL introspection vulnerabilities in Axum applications requires both configuration changes and architectural considerations. The most straightforward approach is to use async_graphql::SchemaBuilder to explicitly disable introspection in production environments.

Here's the secure implementation pattern for Axum applications:

use async_graphql::{SchemaBuilder, EmptyMutation, EmptySubscription};
use async_graphql_axum::graphql;
use axum::{routing::post, Router};
use std::env;

async fn create_schema() -> Schema<QueryRoot, EmptyMutation, EmptySubscription> {
let mut builder = SchemaBuilder::new(QueryRoot);

// Disable introspection in production
if env::var("ENV").unwrap_or_default() == "production" {
builder.disable_introspection();
}

builder.build().unwrap()
}

async fn graphql_handler(schema: Schema<QueryRoot, EmptyMutation, EmptySubscription>) -> impl axum::response::IntoResponse {
graphql(schema)
}

let app = Router::new()
.route("/graphql", post(graphql_handler))
.with_state(create_schema().await);

This approach ensures that introspection is only available in development environments where it's needed for API exploration and client generation.

For applications that need schema information but want to control access, you can implement role-based access control at the Axum layer. Here's an example using a custom middleware for schema access:

use async_graphql::{Request, Response, Schema};
use axum::middleware::Next;
use axum::response::IntoResponse;
use axum::http::StatusCode;

async fn rbac_middleware(req: axum::http::Request, next: Next) -> impl IntoResponse {
let query_str = String::from_utf8(req.body().bytes().unwrap_or_default().to_vec()).unwrap_or_default();

// Check if this is an introspection query
if query_str.contains("__schema") || query_str.contains("__type") {
// Extract authorization header
let auth_header = req.headers().get("authorization");

// Verify user has schema access role
if !has_schema_access_role(auth_header) {
return (StatusCode::FORBIDDEN, "Schema access denied").into_response();
}
}

next.call(req).await
}

Another remediation approach is to create a separate schema for introspection that only exposes a subset of the full schema. This allows you to maintain development capabilities while protecting sensitive information:

use async_graphql::{Schema, SchemaBuilder, EmptyMutation, EmptySubscription};
use async_graphql::Enum;

#[derive(Enum)]
enum SchemaAccess {
Full,
Limited
}

async fn create_limited_schema() -> Schema<QueryRoot, EmptyMutation, EmptySubscription> {
let mut builder = SchemaBuilder::new(QueryRoot);

// Only include public types in limited schema
builder.limit_to_public_types();

// Optionally disable introspection entirely
builder.disable_introspection();

builder.build().unwrap()
}

For comprehensive protection, combine schema configuration with runtime detection. You can use middleBrick's continuous monitoring (available in Pro plans) to regularly scan your Axum GraphQL endpoints for introspection vulnerabilities. The scanner will alert you if introspection becomes re-enabled or if new schema information is exposed.

Finally, consider implementing API versioning and deprecation strategies in your Axum GraphQL API. By clearly marking deprecated fields and removing them from production schemas, you reduce the attack surface that introspection queries can reveal. This approach, combined with proper schema access controls, provides defense-in-depth against GraphQL introspection vulnerabilities.

Related CWEs: dataExposure

CWE IDNameSeverity
CWE-200Exposure of Sensitive Information HIGH
CWE-209Error Information Disclosure MEDIUM
CWE-213Exposure of Sensitive Information Due to Incompatible Policies HIGH
CWE-215Insertion of Sensitive Information Into Debugging Code MEDIUM
CWE-312Cleartext Storage of Sensitive Information HIGH
CWE-359Exposure of Private Personal Information (PII) HIGH
CWE-522Insufficiently Protected Credentials CRITICAL
CWE-532Insertion of Sensitive Information into Log File MEDIUM
CWE-538Insertion of Sensitive Information into Externally-Accessible File HIGH
CWE-540Inclusion of Sensitive Information in Source Code HIGH

Frequently Asked Questions

How can I test if my Axum GraphQL endpoint has introspection enabled?
You can test introspection by sending a POST request to your GraphQL endpoint with a simple introspection query like { __schema { types { name } } }. If you receive a response with schema information, introspection is enabled. For automated testing, use middleBrick's GraphQL scanner which specifically tests for introspection vulnerabilities and provides a security score with remediation guidance.
Does disabling introspection break my GraphQL client tools?
Disabling introspection in production doesn't break existing clients that have cached schemas or use static schema definitions. However, it does prevent tools like GraphiQL, Apollo Studio, and other development tools from automatically discovering your schema. The recommended approach is to disable introspection only in production environments while keeping it enabled in development and staging for tooling support.