Prototype Pollution in Adonisjs with Firestore
Prototype Pollution in Adonisjs with Firestore — how this specific combination creates or exposes the vulnerability
Prototype pollution in AdonisJS when interacting with Google Cloud Firestore occurs when untrusted input modifies JavaScript object prototypes that are later used to construct Firestore document references, queries, or update payloads. Because Firestore client libraries accept plain JavaScript objects for document paths, field values, and query constraints, objects that reach Firestore operations may carry poisoned properties introduced through deep merge patterns or unsafe object extension.
AdonisJS applications commonly use helper libraries to merge configuration or request payloads into query or document models. If these merges are not constrained, an attacker-supplied property such as __proto__, constructor.prototype, or a custom prototype key can propagate into objects passed to Firestore. Once polluted objects are used to build document paths or update maps, the pollution can affect object behavior in unpredictable ways during serialization, validation, or path resolution. For example, a merge that extends a Firestore document map with user input may inadvertently add or override properties on shared prototypes, changing how document data is interpreted or serialized when read back or used in security-sensitive contexts like policy checks.
Consider an endpoint that builds a Firestore document update from request body using object spread or lodash merge. An attacker sending { "__proto__": { isAdmin: true } } can cause the resulting object to carry poisoned properties. When this object is passed to docRef.set(), the polluted fields may bypass expected validation logic or interact unexpectedly with application-level guards that rely on prototype checks. Firestore itself does not execute JavaScript, but the client-side objects used to construct queries and updates can become unsafe if they inherit unexpected properties, potentially leading to privilege escalation or data exposure when combined with insecure authorization logic.
Because middleBrick scans the unauthenticated attack surface and tests input validation as one of its 12 parallel checks, it can surface prototype pollution risks in API endpoints that accept and forward user-controlled data to Firestore operations. Findings include insecure object merging, missing property filtering, and unsafe construction of document paths that may carry tainted data. middleBrick also runs LLM/AI Security checks, ensuring that prototype-polluted prompts or data do not leak into model outputs or get persisted through AI-driven integrations.
Firestore-Specific Remediation in Adonisjs — concrete code fixes
Remediation focuses on preventing untrusted input from reaching Firestore document and query construction. Validate and sanitize all incoming data before it is merged into objects that are passed to Firestore. Use strict parsing for numeric IDs, avoid extending native prototypes, and prefer shallow, controlled merges that exclude dangerous keys like __proto__, constructor, and prototype.
Safe Firestore document update with input filtering
Instead of spreading user input directly, extract only allowed fields and explicitly omit prototype-level keys:
import { schema, rules } from '@ioc:Adonis/Core/Validator'
import { Firestore, doc, updateDoc } from 'firebase-admin/firestore'
const updateSchema = schema.create({
body: schema.object({
documentId: schema.string({ trim: true, escape: false }),
updates: schema.object({
// explicitly allow only expected fields
title: schema.string.optional(),
status: schema.enum.optional(['draft', 'published', 'archived']),
priority: schema.number.optional([rules.range(1, 10)])
}, { extraFields: 'strip' })
})
})
export async function updateDocument(ctx) {
const { documentId, updates } = await ctx.validate({
schema: updateSchema,
data: ctx.request.body()
})
// Ensure no prototype pollution keys are forwarded
const safeUpdates = Object.keys(updates).reduce((acc, key) => {
if (!['__proto__', 'constructor', 'prototype'].includes(key)) {
acc[key] = updates[key]
}
return acc
}, {})
const docRef = doc(Firestore.instance(), 'items', documentId)
await updateDoc(docRef, safeUpdates)
ctx.response.ok({ success: true })
}
Parameterized Firestore document path construction
Build document references using controlled identifiers rather than concatenating raw user input:
import { Firestore, doc, setDoc } from 'firebase-admin/firestore'
import { v4 as uuidv4 } from 'uuid'
export async function createDocument(ctx) {
const body = ctx.request.body()
// Use a server-generated ID or strict slug; avoid direct user input in path
const documentId = body.slug || uuidv4()
const docRef = doc(Firestore.instance(), 'tenants', ctx.tenantId, 'data', documentId)
// Filter dangerous keys from payload
const payload = { ...body }
delete payload.__proto__
delete payload.constructor
delete payload.prototype
await setDoc(docRef, payload)
ctx.response.created({ id: documentId })
}
Query constraint safety
When building query constraints, avoid merging user objects directly into constraint maps:
import { Firestore, collection, query, where, getDocs } from 'firebase-admin/firestore'
export async function listDocuments(ctx) {
const { status } = ctx.request.qs()
const baseCollection = collection(Firestore.instance(), 'records')
// Build constraints explicitly; do not merge untrusted objects into constraint maps
const constraints = []
if (status) {
constraints.push(where('status', '==', status))
}
const q = query(baseCollection, ...constraints)
const snapshot = await getDocs(q)
const rows = snapshot.docs.map(d => ({ id: d.id, ...d.data() }))
ctx.response.ok(rows)
}
These patterns ensure that Firestore interactions remain isolated from prototype pollution vectors. middleBrick can validate these protections by scanning endpoints that accept and forward user input to Firestore, highlighting insecure merges and missing property sanitization in its findings.