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