HIGH excessive data exposureadonisjsfirestore

Excessive Data Exposure in Adonisjs with Firestore

Excessive Data Exposure in Adonisjs 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. In AdonisJS applications that integrate with Google Cloud Firestore, this commonly happens when controller methods query entire documents or collections and serialize them directly into HTTP responses without field-level filtering. Because Firestore documents often contain sensitive fields such as internal IDs, audit metadata, admin flags, or unrelated relational references, returning the full document can unintentionally disclose this information to clients.

Within the Firestore data model, documents live in collections and can contain nested maps and arrays. When an AdonisJS controller uses the Firestore SDK to fetch a document and passes it to JSON serialization (e.g., via ctx.response.send(docData)), all top-level and nested fields are included by default. If the route is unauthenticated or improperly scoped, this behavior can expose sensitive attributes such as permissions, refreshTokens, or business-sensitive fields like pricingTier or internalNotes. Even when authentication is enforced, over-fetching can violate the principle of least privilege if the endpoint does not explicitly restrict which fields are returned.

Another contributing factor is the use of Firestore query snapshots without projection. Reading full documents via get() or streaming snapshots returns every field stored in the document. In AdonisJS routes that support query parameters for filtering (e.g., ?fields=id,name,email), failing to enforce projection on the server side means the client can indirectly influence what is returned, but cannot prevent the server from retrieving and exposing more data than intended. This is especially risky when Firestore security rules are not aligned with the application’s data access layer, because rules may permit read access while the API layer remains unrestricted.

Common real-world scenarios include user profile endpoints that return entire Firestore user documents, administrative dashboards that stream full collections, and GraphQL-style resolvers that expose nested Firestore references without limiting depth or fields. These patterns can lead to data exposure incidents where personal identifiers, session tokens, or configuration details are surfaced to unauthorized clients. Because Firestore does not inherently enforce field-level permissions at the database query layer, the responsibility falls to the AdonisJS service logic to apply strict field selection and validation before data leaves the server.

To detect such issues, scans like those performed by middleBrick analyze the unauthentinated attack surface of an AdonisJS endpoint that interacts with Firestore, looking for routes that retrieve documents without field restrictions. The presence of full document serialization, missing projection logic, or inconsistent rule enforcement are strong indicators of Excessive Data Exposure. Remediation focuses on minimizing the data footprint through explicit field selection, consistent use of query constraints, and validation of output shapes before serialization.

Firestore-Specific Remediation in Adonisjs — concrete code fixes

Remediation centers on ensuring that only required fields are retrieved from Firestore and that the resulting data structure is sanitized before being sent in an HTTP response. Below are concrete, realistic code examples demonstrating secure patterns in an AdonisJS controller.

First, use explicit field selection when querying documents. Instead of returning the entire document snapshot, specify the fields you intend to expose. For example, when fetching a user profile, limit the response to safe, public attributes:

import { DateTime } from 'luxon'
import { Firestore, doc, getDoc } from 'firebase/firestore'

export default class UserProfileController {
  async show({ params, response }) {
    const userDocRef = doc(Firestore, 'users', params.id)
    const snapshot = await getDoc(userDocRef)

    if (!snapshot.exists()) {
      return response.status(404).send({ error: 'Not found' })
    }

    const data = snapshot.data()

    // Explicitly select safe fields
    const safeProfile = {
      id: snapshot.id,
      name: data.name,
      email: data.email,
      avatarUrl: data.avatarUrl,
      updatedAt: DateTime.fromMillis(data.updatedAt.toMillis()).toISO(),
    }

    return response.send(safeProfile)
  }
}

Second, when listing collections, apply projection at the query level if the Firestore SDK supports it via select operations or by manually filtering after retrieval. For collection endpoints, avoid sending entire documents by mapping over the query results and extracting only necessary fields:

import { collection, getDocs } from 'firebase/firestore'

export default class ProductController {
  async index({ response }) {
    const productsCol = collection(Firestore, 'products')
    const snapshot = await getDocs(productsCol)

    const products = snapshot.docs.map((doc) => {
      const data = doc.data()
      return {
        id: doc.id,
        title: data.title,
        price: data.price,
        category: data.category,
        inStock: data.inStock,
      }
    })

    return response.send(products)
  }
}

Third, enforce server-side field filtering when query parameters are used. If your API allows clients to specify which fields to include, validate and sanitize this input strictly, and apply it consistently to Firestore reads:

export default class ArticleController {
  async show({ params, request, response }) {
    const allowedFields = ['id', 'title', 'summary', 'publishedAt', 'authorId']
    const requestedFields = request.qs().fields
    let fields = allowedFields

    if (requestedFields) {
      fields = requestedFields.split(',').filter((f) => allowedFields.includes(f.trim()))
    }

    const docRef = doc(Firestore, 'articles', params.id)
    const snapshot = await getDoc(docRef)

    if (!snapshot.exists()) {
      return response.status(404).send({ error: 'Not found' })
    }

    const data = snapshot.data()
    const result = { id: snapshot.id }

    for (const field of fields) {
      if (data[field] !== undefined) {
        result[field] = data[field]
      }
    }

    return response.send(result)
  }
}

Additionally, consider using Firestore document masks where supported by your client libraries, and ensure that any metadata fields (such as createdBy, deletedAt, or internal flags) are excluded from serialization logic. In AdonisJS, centralize this logic within resource transformers or serializer services to avoid duplication and reduce the risk of accidental data leakage across multiple routes.

Finally, align runtime behavior with security best practices by validating that sensitive endpoints are not inadvertently exposed in OpenAPI specifications or left unguarded by authentication middleware. Regular scans using tools like middleBrick can help surface routes that lack field-level restrictions or that return full document structures, enabling teams to refine their data exposure controls iteratively.

Related CWEs: propertyAuthorization

CWE IDNameSeverity
CWE-915Mass Assignment HIGH

Frequently Asked Questions

Can Firestore security rules alone prevent Excessive Data Exposure in Adonisjs?
Firestore security rules control database access but do not limit which fields are returned by your AdonisJS API. Excessive Data Exposure is best addressed in the application layer by selecting and returning only the fields required for each use case.
How does middleBrick detect Excessive Data Exposure in Adonisjs with Firestore?
middleBrick scans unauthenticated endpoints that interact with Firestore, looking for patterns such as full document retrieval, missing field projection, and inconsistent output filtering. Findings highlight routes where entire documents or unrestricted collections are serialized, providing remediation guidance focused on field-level selection.