HIGH graphql introspectionaxummutual tls

Graphql Introspection in Axum with Mutual Tls

Graphql Introspection in Axum with Mutual Tls — how this specific combination creates or exposes the vulnerability

GraphQL introspection enables clients to query the schema (types, queries, mutations) at runtime. When an Axum service exposes a GraphQL endpoint without restricting introspection and also uses mutual TLS (mTLS) for client authentication, the two controls can interact in ways that weaken overall security posture.

mTLS ensures the client possesses a valid certificate trusted by the server. This is useful for identifying service-to-service callers or specific client applications. However, mTLS alone does not enforce what operations or schema details a permitted client may access. If introspection is allowed for authenticated clients (or for any client presenting a valid certificate), an attacker who obtains or compromises a valid certificate can still run introspection queries to discover the full schema, including sensitive types, queries, and field names.

The combination creates a misleading sense of security: mTLS is often perceived as strong access control, but it does not replace operation-level authorization or schema hiding. In Axum, if your GraphQL handler (for example using juniper or async-graphql) does not explicitly disable introspection and your mTLS configuration permits general service-to-service access, an authenticated caller can probe the API surface without needing credentials for specific business functions. This expands the unauthenticated attack surface in black-box scans, because middleBrick’s GraphQL introspection checks will identify the exposed schema even when mTLS is in place.

Practices that reduce risk include disabling introspection in production GraphQL servers and coupling mTLS with fine-grained authorization checks inside your Axum handlers. Relying on mTLS alone leaves introspection available to any trusted client, which can be leveraged for reconnaissance in multi-tenant or complex service-to-service environments.

Mutual Tls-Specific Remediation in Axum — concrete code fixes

To secure an Axum GraphQL service using mTLS while addressing introspection risks, combine proper Rust TLS configuration with explicit schema controls. Below are concrete, idiomatic examples.

1. Axum server with mTLS and disabling introspection

Use tower-https (which supports Rustls) to enforce mTLS and configure your GraphQL route to disable introspection. This example shows a minimal but production-oriented setup.

use axum::routing::get;
use axum::{Router, Extension};
use tower_http::cors::CorsLayer;
use tower_http::set_header::SetResponseHeaderLayer;
use tower_http::trace::TraceLayer;
use std::net::SocketAddr;
use hyper_rustls::RustlsConfig;
use async_graphql::Schema;
use async_graphql::http::{GraphiQLSource, GraphQLRequest, GraphQLResponse};
use async_graphql::http::graphiql;

// Assume you have built your schema
struct QueryRoot; // your resolver type
#[async_graphql::Object]
impl QueryRoot {
    async fn hello(&self) -> String {
        "world".to_string()
    }
}

#[tokio::main]
async fn main() {
    let schema = Schema::build(QueryRoot, async_graphql::EmptyMutation::new(), async_graphql::EmptySubscription::new()).finish();

    // Build GraphQL route with introspection disabled
    let graphql_route = async_graphql::axum::GraphQL::new(schema)
        .disable_introspection()
        .into_routes();

    let app = Router::new()
        .merge(graphql_route)
        .layer(CorsLayer::permissive())
        .layer(SetResponseHeaderLayer::overriding(
            hyper::header::CONTENT_TYPE,
            "application/json",
        ))
        .layer(TraceLayer::new_for_http());

    let tls_config = RustlsConfig::from_pem_file(
        "server-cert.pem",
        "server-key.pem",
        "ca-cert.pem", // trusted client CA for mTLS
    ).await.expect("Failed to load TLS config");

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    axum::Server::bind_rustls(&addr, tls_config).serve(app).await.unwrap();
}

The key parts are:

  • disable_introspection() ensures introspection queries are rejected, reducing schema exposure.
  • Rustls mTLS is configured by providing a CA certificate that validates client certificates; only clients trusted by this CA can establish TLS connections.
  • Even with mTLS, you should add per-route authorization inside your resolvers (e.g., via extensions containing caller identity from certificate metadata) to implement least-privilege access to specific GraphQL operations.

2. mTLS with per-operation authorization in Axum

If you must allow introspection for certain internal clients, enforce authorization at the field or operation level. Extract caller identity from the TLS session state and validate within resolvers.

use axum::{routing::post, Extension, Json};
use async_graphql::Request;
use async_graphql::http::GraphQLResponse;
use hyper_rustls::server::TlsStream;
use std::sync::Arc;

async fn graphql_handler(
    Extension(state): Extension>,
    Json(req): Json,
    // caller_identity could be passed from a middleware that reads TLS peer cert
    Extension(caller_identity): Extension>,
) -> GraphQLResponse {
    // Example: restrict certain queries to specific clients or roles
    let schema = state.schema.clone();
    let result = if let Some(identity) = caller_identity {
        // Custom logic: e.g., allow introspection only from internal services
        if req.is_introspection() && !identity.starts_with("internal") {
            // Return a controlled error instead of schema details
            async_graphql::Response::errors(vec![async_graphql::Error::new(
                "Introspection not allowed for this client",
            )])
        } else {
            schema.execute(req).await
        }
    } else {
        schema.execute(req).await
    };
    Json(result)
}

This handler demonstrates how to inspect the operation type and caller context to enforce policy beyond transport-layer mTLS. middleBrick’s GraphQL checks will still detect introspection exposure if it remains available to any trusted client; therefore, disabling introspection or tightly controlling its availability is recommended in production.

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

Does mTLS prevent GraphQL introspection?
No. mTLS authenticates clients via certificates but does not restrict what operations they can perform. Introspection must be explicitly disabled or guarded with per-operation authorization to prevent schema discovery.
How does middleBrick treat mTLS-enabled GraphQL endpoints during scans?
middleBrick tests the endpoint as presented. If introspection is reachable by a client presenting valid mTLS credentials (or if credentials are not required for the probe), the scan will report GraphQL introspection findings. mTLS is not a substitute for schema-level controls.