Injection Flaws in Sails with Firestore
Injection Flaws in Sails with Firestore — how this specific combination creates or exposes the vulnerability
Injection flaws occur when untrusted data is interpreted as part of a command or query. In a Sails application using Cloud Firestore, the risk arises when user-controlled input is directly embedded into Firestore queries, aggregations, or document paths without proper validation or escaping. Because Firestore uses a structured query model, concatenating user input into query predicates or collection/document names can lead to unintended data access or execution of unintended operations.
Sails.js is a Node.js MVC framework that does not inherently sanitize inputs for NoSQL backends. When a controller action builds a Firestore query by injecting request parameters into where(), orderBy(), or document references, an attacker can manipulate these values to bypass intended filters. For example, an attacker might supply a crafted value to a where clause that always evaluates to true, effectively reading documents they should not see.
Firestore’s client SDK evaluates JavaScript expressions on the client or in Node.js, and if user input is used to construct field paths or collection identifiers, it may allow traversal outside the intended data scope. An attacker could provide a dot notation field path (e.g., __proto__.admin) to probe or modify object properties if the application performs insecure merging of objects. Additionally, Firestore allows querying array-contains; injecting array values could expose documents containing sensitive array entries.
Another scenario involves dynamic collection names. If a Sails controller uses user input to determine the Firestore collection to query (e.g., firestore.collection(req.body.entity)), an attacker could reference collections outside the application’s expected namespace, potentially accessing shared or administrative collections. Even with Firestore security rules, improperly structured queries can return more data than intended if rules are not aligned with query patterns.
Because middleBrick scans the unauthenticated attack surface and includes checks for Input Validation and Property Authorization across 12 parallel security checks, it can detect whether a Sails + Firestore endpoint allows injection-style manipulation. The scanner does not fix the code but provides prioritized findings with severity and remediation guidance, helping developers identify where query construction must be hardened.
Firestore-Specific Remediation in Sails — concrete code fixes
To prevent injection flaws when using Firestore with Sails, always treat user input as untrusted and avoid string concatenation or direct interpolation into queries. Use Firestore’s built-in query methods with typed parameters, and validate inputs against an allowlist before use.
1. Parameterized Queries and Field Path Validation
Instead of dynamically building field paths from user input, validate field names against a known set and use object properties safely. Never allow user input to directly become a field path string.
const allowedFields = ['email', 'status', 'createdAt'];
if (!allowedFields.includes(req.body.sortField)) {
throw new Error('Invalid sort field');
}
const query = firestore.collection('users')
.where('status', '==', req.body.statusValue)
.orderBy(req.body.sortField, 'asc');
const snapshot = await query.get();
2. Safe Document References and Collection Names
Do not use user input to construct document references or collection names. If you must use dynamic collections, map input through a strict whitelist and use Firestore’s document ID validation rules.
const validCollections = { profiles: true, orders: true };
const collectionName = req.body.collection;
if (!validCollections[collectionName]) {
throw new Error('Invalid collection');
}
const docRef = firestore.collection(collectionName).doc(req.params.docId);
const doc = await docRef.get();
if (!doc.exists) { return res.notFound(); }
3. Avoiding Prototype Pollution via Object Merging
When updating documents, prefer explicit field assignment over merging user-provided objects. If merging is necessary, use a library or manual filtering to strip dangerous keys like __proto__, constructor, and prototype.
const userInput = req.body.updateData;
const safeData = {};
// Explicitly pick expected fields
if (typeof userInput.displayName === 'string') {
safeData.displayName = userInput.displayName;
}
if (typeof userInput.preferences === 'object' && userInput.preferences !== null) {
safeData.preferences = userInput.preferences;
}
await firestore.collection('settings').doc(userId).update(safeData);
4. Using Firestore Security Rules as a Safety Net
While rules are not a substitute for secure coding, ensure rules restrict reads and writes to intended paths and validate data shapes. Combine rules with parameterized queries in Sails for defense in depth.
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 /publicItems/{itemId} {
allow read: if true;
allow write: if request.auth != null && request.auth.token.admin == true;
}
}
}