Request Smuggling in Axum with Firestore
Request Smuggling in Axum with Firestore — how this specific combination creates or exposes the vulnerability
Request smuggling occurs when an application processes HTTP requests differently depending on whether they pass through an intermediary proxy or are handled directly. In an Axum application that uses Firestore as a backend data store, the risk arises from how Axum parses and forwards requests and how Firestore operations are constructed from potentially untrusted inputs.
Axum is a Rust web framework that relies on strongly typed extractors for routing and request handling. When request body parsing, header handling, or routing logic is not strictly canonicalized, an attacker may craft requests that are interpreted differently by Axum and an upstream proxy or load balancer. This discrepancy can allow a smuggled request to bypass intended routing or reach an endpoint that would otherwise be protected. For example, if Axum tolerates ambiguous Transfer-Encoding and Content-Length headers, a proxy that normalizes these headers differently may forward a modified request to Firestore-bound handlers with an unintended method, path, or body.
Firestore interactions in Axum typically occur within handler functions where data is read or written using the Firestore client. If user-controlled inputs such as document IDs, collection paths, or query parameters are reflected into Firestore operations without strict validation, a smuggled request can manipulate which document is accessed or which write operation is executed. Consider a handler that builds a Firestore document reference from a route parameter and an optional query parameter:
use axum::{routing::get, Router};\nuse google_cloud_rust::firestore::client::Client;\nasync fn document_handler(\n Path(collection): Path,\n Query(params): Query<HashMap<String, String>>,\n) -> String {\n let client = Client::new().await.unwrap();\n let doc_id = params.get("doc_id").unwrap_or("default");\n let doc_ref = client.collection(&collection).doc(doc_id);\n // read document logic\n "ok".to_string()\n}\n
If the proxy and Axum interpret header boundaries differently, an attacker may smuggle a second request by appending additional headers or a second request body. The smuggled request could change the effective collection or document ID used by Firestore, potentially accessing or modifying data outside the intended scope. Because Firestore operations are constructed after Axum extracts parameters, smuggling can lead to unauthorized data exposure or unintended writes without the service explicitly rejecting the request.
Additionally, if Axum routes are defined with overlapping patterns or lax matching rules, a smuggled request may reach a handler that expects a different content type or authentication context. Firestore operations that assume a certain request structure may then process data under incorrect assumptions, amplifying the impact of the smuggling. The key issue is not Firestore itself, but the way Axum normalizes and interprets incoming HTTP messages before constructing Firestore calls.
Firestore-Specific Remediation in Axum — concrete code fixes
To mitigate request smuggling when Axum integrates with Firestore, focus on canonicalizing request parsing, validating all inputs used in Firestore operations, and ensuring consistent handling of headers and routing. Below are concrete remediation steps and code examples tailored to Axum and Firestore.
1. Strict header and body parsing
Ensure Axum rejects or normalizes ambiguous Transfer-Encoding and Content-Length headers. Use Axum’s built-in extractors and avoid low-level header manipulation that could introduce parsing ambiguities.
use axum::{\n async_trait,\n extract::FromRequestParts,\n http::{request::Parts, HeaderMap, HeaderName, StatusCode},\n};\n\nasync fn validate_headers(headers: &HeaderMap) -> Result<(), StatusCode> {\n let has_te = headers.contains_key(HeaderName::from_static("transfer-encoding"));\n let has_cl = headers.contains_key(HeaderName::from_static("content-length"));\n if has_te && has_cl {\n return Err(StatusCode::BAD_REQUEST);\n }\n Ok(())\n}\n
2. Canonicalize routing and method handling
Define routes with strict path and method semantics. Avoid catch-all or greedy matchers that could allow smuggling via unexpected path segments.
use axum::routing::post;\nuse std::net::SocketAddr;\nuse tower_http::trace::TraceLayer;\n\nasync fn create_document_handler(\n collection: String,\n Json(payload): Json<serde_json::Value>,\n) -> String {\n let client = firestore_client().await;\n let doc_ref = client.collection(&collection).doc(uuid::Uuid::new_v4().to_string());\n doc_ref.set(payload).await.unwrap();\n "created".to_string()\n}\n\n#[tokio::main]\nasync fn main() {\n let app = Router::new()\n .route("/api/v1/documents/:collection", post(create_document_handler))\n .layer(TraceLayer::new_for_http());\n let addr = SocketAddr::from(([127, 0, 0, 1], 3000));\n axum::Server::bind(&addr).serve(app.into_make_service()).await.unwrap();\n}\n
3. Validate Firestore identifiers
Treat collection and document IDs as untrusted input. Normalize and validate them before constructing Firestore references to prevent path manipulation via smuggling.
use google_cloud_rust::firestore::client::Client;\nuse regex::Regex;\n\nasync fn safe_document_access(\n raw_collection: String,\n raw_doc_id: Option<String>,\n) -> Result<String, String> {\n let coll_re = Regex::new(r"^[a-z][a-z0-9_-]{1,100}$").map_err(|_| "regex error")?;\n let id_re = Regex::new(r"^[a-zA-Z0-9_-]{1,500}$").map_err(|_| "regex error")?;\n\n if !coll_re.is_match(&raw_collection) {\n return Err("invalid collection name".into());\n }\n let doc_id = raw_doc_id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());\n if !id_re.is_match(&doc_id) {\n return Err("invalid document ID".into());\n }\n let client = Client::new().await.map_err(|e| e.to_string())?;\n let doc_ref = client.collection(&raw_collection).doc(&doc_id);\n // proceed with Firestore operation\n Ok(doc_ref.id().to_string())\n}\n
4. Consistent middleware for request normalization
Apply middleware that enforces a single interpretation of headers and body size before requests reach Axum’s routing layer. This reduces the surface for discrepancies between proxy and application parsing.
use axum::{middleware, Extension, Json};\nuse std::convert::Infallible;\nuse http::{Request, Response};\n\nasync fn normalize_middleware(req: Request<B>, next: middleware::Next<B>) -> Result<Response, Infallible> {\n // enforce content-length, reject ambiguous transfer-encoding\n let req = req; // apply normalization logic here\n let res = next.run(req).await;\n Ok(res)\n}\n\nlet app = Router::new()\n .layer(middleware::from_fn(normalize_middleware))\n .route("/api/data", post(secure_handler));\n
By combining strict header validation, canonical routing, and input sanitization, Axum applications that interact with Firestore can reduce the risk of request smuggling while maintaining correct access to Firestore resources.