Mass Assignment in Actix with Firestore
Mass Assignment in Actix with Firestore — how this specific combination creates or exposes the vulnerability
Mass assignment occurs when an API binds incoming HTTP request data (e.g., JSON body or query parameters) directly to a data model or database record without explicit field allowlisting. In an Actix web service that uses Cloud Firestore as the backend, this typically manifests in handler code that deserializes request payloads into a struct and then writes that struct directly to Firestore using a document set or update operation. Because Firestore document writes can target nested fields and support partial updates, unchecked deserialization can overwrite sensitive fields such as role flags, permissions, administrator status, or metadata that should remain immutable.
For example, consider an Actix handler that accepts a JSON payload to update a user profile and passes the body to Firestore without filtering fields:
// Risky Actix handler: binds all JSON fields to UserUpdate and writes to Firestore
async fn update_profile(
user_id: web::Path,
item: web::Json,
) -> impl Responder {
let db = FirestoreDb::new();
let doc_ref = db.collection("users").doc(&user_id);
// Danger: writes every field present in JSON, including admin or permissions
if let Err(e) = doc_ref.set(&item.into_inner()).await {
// handle error
}
HttpResponse::Ok().finish()
}
If the UserUpdate struct includes fields like admin or permissions, and the client sends those keys in the JSON, Firestore will accept and persist them. This maps directly to the OWASP API Security Top 10:2023 API1:2023 Broken Object Level Authorization (BOLA)/Insecure Direct Object References (IDOR), where attackers modify privilege-bearing fields via mass assignment. Attack patterns include tampering with JSON before transmission or using tools like curl to inject "admin": true or "role": "superuser". The risk is especially severe when Firestore security rules rely solely on the client-provided document path and do not validate field-level writes, effectively trusting the caller to enforce permissions that should be enforced server-side.
Compounding the issue, Firestore’s support for nested maps and arrays means a single mass-assignment write can overwrite deeply nested data. In Actix, if the deserialization is permissive (e.g., using serde with #[serde(flatten)] or untagged enums), an attacker can introduce unexpected keys that change behavior beyond simple privilege escalation, such as modifying audit metadata or changing visibility flags. Because the scan category BOLA/IDOR and Property Authorization are evaluated in parallel with Authentication and Input Validation, middleBrick would flag this as a high-severity finding, noting both authorization bypass potential and unsafe input consumption.
Firestore-Specific Remediation in Actix — concrete code fixes
Remediation focuses on strict input filtering, server-side field allowlisting, and using Firestore’s update APIs that target only intended fields. Avoid binding the entire JSON payload directly to a Firestore write. Instead, deserialize into a partially defined DTO, explicitly copy allowed fields, and use Firestore’s update with field paths to prevent overwriting sensitive attributes.
Secure handler example in Actix with Firestore:
use actix_web::{web, HttpResponse, Responder};
use firestore::{FirestoreDb, serde_json::json};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct ProfileUpdate {
display_name: Option,
email: Option,
// Do NOT include admin, role, or permissions here
}
#[derive(Serialize)]
struct UserDocument {
display_name: String,
email: String,
// server-managed fields
created_at: i64,
admin: bool,
}
async fn update_profile(
user_id: web::Path,
item: web::Json,
) -> impl Responder {
let db = FirestoreDb::new();
let doc_ref = db.collection("users").doc(&user_id);
// Build only the fields we allow to change
let mut updates = firestore::UpdateMap::new();
if let Some(name) = &item.display_name {
updates.insert("display_name", name);
}
if let Some(email) = &item.email {
updates.insert("email", email);
}
// Use update to avoid overwriting admin or other server-managed fields
if updates.is_empty() {
return HttpResponse::BadRequest().body("No valid fields to update");
}
match doc_ref.update(updates).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(e) => HttpResponse::InternalServerError().body(format!("Firestore error: {:?}", e)),
}
}
Key points:
ProfileUpdateonly contains safe, client-editable fields; sensitive fields likeadminare omitted.- Firestore’s
updateaccepts a map of field paths and values, ensuring only specified keys are changed. - Server-managed fields (e.g.,
admin,created_at) remain untouched and are not deserialized from user input.
For more complex scenarios, such as merging nested objects, explicitly reference the field path:
updates.insert("preferences.theme", &theme_value);
updates.insert("profile.verified", &true_value);
This approach aligns with the scan categories Input Validation and Property Authorization, ensuring that user-controlled input cannot modify immutable attributes. middleBrick will validate that no mass-assignment vectors remain and that the implementation follows least-privilege update patterns.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |