HIGH broken access controlrocketfirestore

Broken Access Control in Rocket with Firestore

Broken Access Control in Rocket with Firestore — how this specific combination creates or exposes the vulnerability

Broken Access Control (BOLA/IDOR) in a Rocket application that uses Google Cloud Firestore typically occurs when route handlers reference user identifiers directly from the request (e.g., path parameters or tokens) and use those values to construct Firestore reads or writes without verifying authorization. Firestore security rules alone do not protect unauthenticated or improperly authenticated endpoints; if your Rocket endpoints rely solely on the client-supplied user ID to build document paths, an attacker can change that ID to access or modify another user’s data.

Consider a route GET /users/:user_id/profile in Rocket. If the handler uses user_id to form a Firestore path like users/{user_id} without confirming that the authenticated principal owns that ID, the endpoint exposes a direct horizontal IDOR vector. Firestore rules may allow read if request.auth != null, but they do not automatically enforce that request.auth.uid == user_id. The vulnerability is realized when the application logic omits this equality check, and the unauthenticated or low-privilege attacker can enumerate or manipulate data across user boundaries.

Another common pattern arises when Firestore documents embed references to other user-owned resources (e.g., a rooms collection where each document has an owner_uid). If a Rocket endpoint lists or deletes rooms by querying Firestore with filters like .where("owner_uid", "==", room_id_from_request) without validating that the requesting user is the owner, the attacker can supply arbitrary room IDs and potentially delete or view rooms they should not access. Firestore’s rule to require authentication does not prevent this logical access control flaw; it only ensures a user is logged in, not that they are authorized for the specific resource.

When exposing Firestore data through an API, misconfigured CORS or overly permissive Firestore rules can further amplify the risk. For instance, rules that allow read on a collection if request.auth != null but lack resource-level checks enable unauthenticated attackers to leverage compromised tokens or confused-deputy scenarios to pivot across users. The combination of Rocket’s route flexibility and Firestore’s document-level addressing makes it straightforward to inadvertently expose a large attack surface if authorization is not enforced explicitly in handler code rather than relying on rules alone.

To discover such issues, scanners perform black-box testing by sending crafted requests with modified identifiers and observing whether data is returned or mutated. A security risk score reflects the severity when endpoints expose sensitive data or allow unauthorized operations due to missing ownership verification. Proper remediation requires validating the authenticated subject against the resource owner on every request and ensuring least-privilege access patterns in both application logic and Firestore rules.

Firestore-Specific Remediation in Rocket — concrete code fixes

Remediation centers on enforcing ownership checks in your Rocket handlers and designing Firestore rules to complement, not replace, application-level authorization. Always resolve the authenticated user’s identity from the request (e.g., via JWT or session) and use it to scope all database operations. Never trust route parameters to determine which data a user can access.

Example: secure Rocket handler with Firestore in Rust using the official Google client library. The handler extracts the authenticated UID, retrieves the document for that UID, and returns a 403 if the IDs do not match.

use rocket::http::Status;
use rocket::request::Request;
use rocket::response::Responder;
use google_firestore1::FirestoreService;
use google_auth_rs::Authenticator;

#[rocket::get("/users/<user_id>/profile")]
async fn get_profile(
    user_id: String,
    firestore: &rocket::State<FirestoreService>,
    auth: Auth<JsonWebToken>, // Assume a fair that extracts authenticated UID
) -> Result<Json<Profile>, (Status, String)> {
    let auth = auth.ok_or_else(|| (Status::Unauthorized, "Missing auth"))?;
    let uid: String = auth.claims.subject; // The authenticated user’s UID
    if uid != user_id {
        return Err((Status::Forbidden, "Access denied"));
    }
    let doc_ref = firestore
        .project(<PROJECT>.to_string())
        .location(<REGION>.to_string())
        .database("(default)")
        .doc("users")
        .doc(&user_id);
    let doc = doc_ref.get().await.map_err(|e| (Status::InternalServerError, e.to_string()))?;
    if !doc.exists() {
        return Err((Status::NotFound, "Profile not found"));
    }
    let profile: Profile = doc.convert()?;
    Ok(Json(profile))
}

This handler ensures the authenticated UID matches the requested user ID before reading from Firestore, preventing horizontal IDOR even if the route parameter is manipulated.

Example: secure handler for deleting a room where ownership is stored in the document. Fetch the document first, verify the owner, then delete. Avoid queries that rely only on client-supplied filters.

#[rocket::delete("/rooms/<room_id>")]
async fn delete_room(
    room_id: String,
    firestore: &rocket::State<FirestoreService>,
    auth: Auth<JsonWebToken>,
) -> Result<Status, (Status, String)> {
    let auth = auth.ok_or_else(|| (Status::Unauthorized, "Missing auth"))?;
    let uid: String = auth.claims.subject;
    let doc_ref = firestore
        .project(<PROJECT>.to_string())
        .location(<REGION>.to_string())
        .database("(default)")
        .doc("rooms")
        .doc(&room_id);
    let doc = doc_ref.get().await.map_err(|e| (Status::InternalServerError, e.to_string()))?;
    if !doc.exists() {
        return Err((Status::NotFound, "Room not found"));
    }
    let owner: String = doc.get("owner_uid").ok_or_else(|| (Status::BadRequest, "Invalid document"))?;
    if owner != uid {
        return Err((Status::Forbidden, "Not the room owner").into());
    }
    doc_ref.delete().await.map_err(|e| (Status::InternalServerError, e.to_string()))?;
    Ok(Status::NoContent)
}

On the Firestore side, complement these checks with rules that require authentication and, when possible, tie access to document fields. However, do not rely on rules alone to enforce per-resource ownership for APIs.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
    match /rooms/{roomId} {
      allow read, write: if request.auth != null && request.resource.data.owner_uid == request.auth.uid;
    }
  }
}

By combining explicit ownership checks in Rocket handlers with disciplined Firestore rules, you reduce the risk of BOLA/IDOR. Regular scans using tools like middleBrick can help detect whether endpoints inadvertently trust client-supplied identifiers, providing prioritized findings mapped to frameworks such as OWASP API Top 10 to guide remediation.

Frequently Asked Questions

Can Firestore security rules alone prevent Broken Access Control in Rocket APIs?
No. Firestore rules provide a baseline but do not enforce that the authenticated user is the owner of the specific resource. You must validate ownership in your Rocket handlers by comparing the authenticated UID to the resource owner before performing any read or write.
How can I test if my Rocket + Firestore endpoints are vulnerable to IDOR?
Use unauthenticated or low-privilege requests to access or modify resources using other users’ identifiers. If the endpoint returns data or performs the operation without verifying ownership, it is vulnerable. Automated scanners like middleBrick can surface these issues by probing endpoints with modified identifiers and checking for unauthorized data exposure.