Excessive Data Exposure in Axum with Firestore
Excessive Data Exposure in Axum with Firestore — how this specific combination creates or exposes the vulnerability
Excessive Data Exposure occurs when an API returns more data than necessary for a given operation, often including sensitive fields, internal identifiers, or related records that should remain restricted. In an Axum service that integrates with Google Cloud Firestore, this commonly happens when query handlers serialize entire Firestore documents or documents with nested references and send them directly to the client without selective projection or redaction.
Consider an endpoint that retrieves a user profile. If the handler fetches a Firestore document from a users collection and returns the full document as JSON, it may unintentionally expose fields such as password_hash, email, internal_role, or references to other sensitive collections. Because Firestore documents can contain deeply nested maps and arrays, the risk of oversharing increases when the document is serialized without explicit filtering. This is especially relevant when using the Firestore Rust SDK to deserialize documents into structs; if the struct includes fields that are not intended for client consumption and those fields are not properly omitted during serialization, the data will be included in the HTTP response.
Another common pattern in Axum involves returning lists of documents, such as a search or index endpoint that queries a products collection. If the handler returns each document in full, related data stored in subcollections or additional fields like cost_price or supplier_details may be exposed. Because Firestore does not enforce field-level access controls at the database level for read operations performed with the server-side SDK, the responsibility falls to the application layer to enforce least privilege by constructing responses that contain only necessary fields.
The combination of Axum's flexible JSON serialization and Firestore's document-oriented model amplifies the impact of missing field filtering. Without explicit projection using select on the Firestore query or manual struct construction that omits sensitive fields, developers risk creating endpoints that leak authentication tokens, personal data, or operational metadata. This aligns with the OWASP API Top 10 category of Excessive Data Exposure, and it is frequently identified in scans that compare the declared OpenAPI schema with runtime responses to detect mismatches in returned fields.
Firestore-Specific Remediation in Axum — concrete code fixes
To mitigate Excessive Data Exposure when using Firestore with Axum, apply explicit field selection and careful struct design. Prefer using Firestore's select to limit returned fields at the query level, and define response-specific structs that include only the fields intended for the client. Avoid returning raw Firestore document snapshots or fully populated domain structs directly from handlers.
Example: Safe Firestore document retrieval with field selection and filtered struct
use axum::{response::Json, routing::get, Router};
use google_cloud_firestore::client::Client;
use serde::Serialize;
// Define a response DTO that includes only safe, necessary fields
#[derive(Serialize)]
struct UserProfileResponse {
user_id: String,
display_name: String,
avatar_url: String,
}
async fn get_user_profile(
client: &Client,
user_id: String,
) -> Result<Json<UserProfileResponse>, (axum::http::StatusCode, String)> {
// Use select to limit returned fields at the Firestore query level
let doc = client
.collection("users")
.doc(&user_id)
.select(&["display_name", "avatar_url"])
.get()
.await
.map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
if !doc.exists() {
return Err((axum::http::StatusCode::NOT_FOUND, "User not found".to_string()));
}
// Construct a response DTO from the selected fields only
let response = UserProfileResponse {
user_id: doc.id().to_string(),
display_name: doc.get("display_name").unwrap_or("").to_string(),
avatar_url: doc.get("avatar_url").unwrap_or("").to_string(),
};
Ok(Json(response))
}Example: Index endpoint with selective serialization for a product catalog
use axum::{response::Json, routing::get, Router};
use google_cloud_firestore::client::Client;
use serde::Serialize;
// Response DTO for product listing
#[derive(Serialize)]
struct ProductSummary {
product_id: String,
name: String,
price: f64,
}
async fn list_products(
client: &Client,
) -> Result<Json<Vec<ProductSummary>>, (axum::http::StatusCode, String)> {{
// Select only required fields to avoid exposing internal data
let docs = client
.collection("products")
.select(&["name", "price"])
.get()
.await
.map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let products: Vec<ProductSummary> = docs
.iter()
.filter_map(|doc| {
if doc.exists() {
Some(ProductSummary {
product_id: doc.id().to_string(),
name: doc.get("name").unwrap_or("Unknown").to_string(),
price: doc.get("price").unwrap_or(0.0),
})
} else {
None
}})
.collect();
Ok(Json(products))
}Additional practices
- Define separate structs for database entities and API responses to prevent accidental exposure of internal fields.
- Use Firestore queries with
selectto minimize data transfer and ensure only intended fields are retrieved. - Validate and sanitize any data that may be derived from Firestore before including it in a response, especially if it is sourced from nested maps or subcollections.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |