Excessive Data Exposure in Actix with Mongodb
Excessive Data Exposure in Actix with Mongodb — 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 that should remain restricted. In an Actix web service that uses MongoDB as a backend, this typically arises from permissive query projections, incomplete document filtering, or serializing entire domain objects directly into responses. Because Actix handlers often map HTTP requests directly to MongoDB documents, developers may inadvertently return fields such as internal identifiers, password hashes, API keys, or audit metadata that were intended for server-side use only.
Consider a user profile endpoint that queries MongoDB without restricting the returned fields. If the handler deserializes full BSON documents into a struct and then serializes that struct back as JSON, fields like password_hash, email_verified_at, or internal roles may be exposed to the client. This becomes especially risky when the API does not enforce field-level authorization per request context, meaning an unprivileged caller can read attributes meant for administrators or other privileged roles. The risk is compounded when responses include related references (e.g., embedded or lazily loaded data) that are resolved automatically by the application layer, exposing additional data sets without explicit request intent.
Another common pattern in Actix with MongoDB involves using generic query builders that return full documents for convenience. For example, a search route might construct a filter on non-sensitive fields but omit projection specifications, causing MongoDB to return every stored field. If the handler passes these documents through a JSON serializer without redaction, the response carries unnecessary sensitive content. Because Actix does not enforce schema-level output policies by default, it is up to the developer to define precise projections or transformation logic that aligns with the principle of least privilege. Without such controls, the API surface unintentionally reveals data that can be leveraged in downstream attacks, including identity enumeration, social engineering, or lateral movement within a system.
Additionally, aggregation pipelines or lookup stages in MongoDB may return joined data that includes sensitive fields from related collections. In an Actix handler, if these combined results are exposed as-is, the response can expose information that individual collections would not normally reveal on their own. For instance, a report endpoint that joins user data with activity logs might return private behavioral details unless the pipeline explicitly prunes or masks those fields. The combination of Actix’s routing flexibility and MongoDB’s rich document model increases the surface for accidental exposure when projection and response shaping are not rigorously defined on a per-endpoint basis.
Mongodb-Specific Remediation in Actix — concrete code fixes
To mitigate Excessive Data Exposure in Actix with MongoDB, explicitly define projection specifications for every query and avoid returning full documents unless absolutely necessary. Use MongoDB’s projection to include only required fields, and apply additional filtering during serialization to ensure sensitive attributes are never part of the response. Below are concrete, realistic code examples that demonstrate secure patterns.
1. Explicit projection with FindOptions
When retrieving a user document, specify only the fields that the client is allowed to see. This prevents leakage of password hashes, tokens, or internal metadata.
use mongodb::{bson::{doc, Document}, options::FindOptions};
use actix_web::{web, HttpResponse};
async fn get_user_profile(
collection: web::Data>,
user_id: String,
) -> HttpResponse {
let filter = doc! { "_id": user_id };
let projection = doc! { "username": 1, "email": 1, "profile": 1, "_id": 1 };
let options = FindOptions::builder().projection(projection).build();
match collection.find_one(filter, options).await {
Ok(Some(doc)) => HttpResponse::Ok().json(doc),
Ok(None) => HttpResponse::NotFound().finish(),
Err(_) => HttpResponse::InternalServerError().finish(),
}
}
2. Struct-based deserialization with selective fields
Define a response-specific struct that includes only safe fields. This ensures compile-time guarantees and avoids accidental exposure through generic deserialization.
use serde::{Deserialize, Serialize};
use actix_web::web;
#[derive(Serialize, Deserialize)]
struct PublicUser {
username: String,
email: String,
profile: Profile,
}
#[derive(Serialize, Deserialize)]
struct Profile {
display_name: String,
avatar_url: String,
}
async fn get_public_user(
user_id: String,
collection: web::Data>,
) -> actix_web::Result> {
let filter = doc! { "_id": user_id };
let projection = doc! { "username": 1, "email": 1, "profile": 1, "password_hash": 0, "api_key": 0 };
let doc = collection.find_one(filter, Some(mongodb::options::FindOptions::builder().projection(projection).build()))
.await
.map_err(|_| actix_web::error::ErrorInternalServerError("db error"))?;
let user: PublicUser = bson::from_bson(bson::Bson::Document(doc.ok_or_else(|| actix_web::error::ErrorNotFound("not found"))?))
.map_err(|_| actix_web::error::ErrorInternalServerError("deserialization error"))?;
Ok(web::Json(user))
}
3. Aggregation with $project to reshape joined data
When using MongoDB aggregations in Actix, explicitly prune sensitive fields at each stage to avoid unintentional exposure through joins or lookups.
use mongodb::bson::doc;
let pipeline = vec![
doc! { "$match": { "user_id": user_id } },
doc! { "$lookup": mongodb::bson::doc! { "from": "activity_logs", "localField": "_id", "foreignField": "user_id", "as": "logs" } },
doc! { "$project": {
"username": 1,
"logs.timestamp": 1,
"logs.action_type": 1,
"logs.ip_address": 1,
"logs.internal_metadata": 0,
"logs.user_token": 0
}},
];
let cursor = collection.aggregate(pipeline, None).await;
// Process cursor and return only allowed fields
4. Runtime redaction for dynamic fields
In cases where responses are built from multiple sources, apply explicit redaction before serialization to remove any sensitive keys that may have been included inadvertently.
fn redact_sensitive(mut doc: Document) -> Document {
doc.remove("password_hash");
doc.remove("api_key");
doc.remove("internal_role");
doc
}
// Usage after query
let raw = collection.find_one(filter, None).await?;
let safe_doc = raw.map(redact_sensitive);
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |