Graphql Batching in Axum

How GraphQL Batching Manifests in Axum

GraphQL batching, a feature allowing multiple operations in a single HTTP request, is a common performance optimization but introduces significant security risks in Axum-based APIs. Axum, a modular Rust framework, often pairs with async-graphql or juniper crates to implement GraphQL servers. These libraries typically accept an array of operations in the request body, processing them sequentially or in parallel without inherent limits on batch size.

An attacker exploits this by sending a single request containing hundreds or thousands of simple queries (e.g., { __typename }). While each query is cheap, the cumulative CPU, memory, and database connection load can exhaust server resources, leading to a Denial-of-Service (DoS). This maps directly to OWASP API4:2023 - Unrestricted Resource Consumption and CWE-770: Allocation of Resources Without Limits or Throttling.

In Axum with async-graphql, the vulnerability often appears in the root handler. Consider this typical setup:

use async_graphql::{Schema, EmptyMutation, EmptySubscription, Request, ServerFuture};
use axum::{routing::post, Router};

let schema = Schema::build(EmptyQuery, EmptyMutation, EmptySubscription).finish();

let app = Router::new().route("/graphql", post(|req: Request| async move {
    ServerFuture::new(schema.execute(req)).into_response()
}));

The schema.execute(req) call processes the entire batch by default. The async-graphql library does not impose a default limit on the number of operations per batch. If an attacker sends a JSON body like [{"query": "{ __typename }"}, ...] with 10,000 entries, the server will attempt all 10,000, potentially overwhelming the async runtime or downstream databases.

Another Axum-specific manifestation occurs when developers manually parse batches. Some implementations iterate over an array and execute each operation independently, multiplying the impact of any single-query inefficiency (e.g., an N+1 query problem becomes an N*10000 problem).

Axum-Specific Detection

Detecting unrestricted GraphQL batching in an Axum API requires testing the endpoint's response to large, syntactically valid batch requests. middleBrick's scanner performs this as part of its Rate Limiting and Input Validation checks. It sends a configurable number of trivial operations (e.g., { __typename }) in a single POST request to the /graphql endpoint and measures response time, error rates, and resource consumption indicators.

For an Axum service using async-graphql, a vulnerable endpoint will typically:

  • Return HTTP 200 for all operations in the batch, even when the batch size is massive (e.g., 5000+).
  • Show a linear or exponential increase in response time as batch size grows.
  • Potentially return partial errors (e.g., {"errors":[...]}) only after hitting internal limits like connection pool exhaustion, not at a configured threshold.

You can manually replicate middleBrick's detection with a simple curl test. Generate a batch of 2000 identical queries:

printf '[{