Pii Leakage in Express with Firestore
Pii Leakage in Express with Firestore — how this specific combination creates or exposes the vulnerability
When an Express application uses Google Cloud Firestore as a backend, PII leakage commonly arises from insecure data modeling and overly permissive read paths. Firestore’s document and collection structure can inadvertently expose sensitive fields such as email, phone, or government identifiers if rules and application logic do not enforce least-privilege access.
In this stack, developers often map user profiles or session objects directly to Firestore documents. Without field-level filtering, an API endpoint that returns a full document may expose PII to unauthenticated or low-privilege callers. For example, an endpoint like /api/users/:userId that performs db.collection('users').doc(userId).get() and sends the entire snapshot back can leak data when the caller should only see a display name.
Another common pattern is using Firestore security rules that focus solely on document existence or ownership, but omit explicit field masks. Rules such as allow read: if request.auth != null; permit a fully authenticated user to read all fields, including PII that might be needed only by a privileged service. If the Express route does not sanitize the document server-side before sending the response, the API becomes a direct channel for PII leakage.
A third contributor is the use of Firestore queries that return multiple documents without projection. Queries like db.collection('profiles').where('public', '==', false).get() can return private fields when the query is allowed by rules but the Express handler streams the results unchanged to the client. Combined with weak authentication or misconfigured CORS, this enables enumeration or exfiltration of sensitive records.
middleBrick detects these risks through its unauthenticated, black-box scan, exercising endpoints and inspecting Firestore rules behavior where observable. It flags exposed PII by analyzing the API surface and cross-referencing findings with the OpenAPI spec, identifying inconsistencies between intended and actual data exposure. The scanner also checks for missing input validation on identifiers that could lead to horizontal privilege escalation across user documents.
Remediation involves both server-side filtering in Express and tightening Firestore rules. You should return only necessary fields, apply role-based field masking, and validate ownership using robust identifiers. middleBrick’s findings include prioritized guidance and references to compliance mappings such as OWASP API Top 10 and GDPR, helping you understand the severity and required changes without implying automatic fixes.
Firestore-Specific Remediation in Express — concrete code fixes
To prevent PII leakage, implement field selection and strict rule checks in your Express handlers. Below are concrete, working examples that demonstrate secure patterns when working with Firestore.
First, use projection to limit returned fields. This ensures only non-sensitive data is sent to the client:
const express = require('express');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
const router = express.Router();
router.get('/api/users/:userId/profile', async (req, res) => {
try {
const userId = req.params.userId;
const snapshot = await db.collection('users')
.doc(userId)
.get({ fields: ['displayName', 'avatarUrl'] });
if (!snapshot.exists) {
return res.status(404).json({ error: 'Not found' });
}
res.json(snapshot.data());
} catch (err) {
res.status(500).json({ error: 'Internal server error' });
}
});
module.exports = router;
Second, enforce ownership and field-level checks in Firestore security rules. Combine resource-based conditions with explicit field access patterns:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.auth != null && request.auth.uid == userId
&& request.resource.data.keys().hasAll(['displayName', 'avatarUrl'])
&& request.resource.data.diff(resource.data).affectedKeys().hasNone(['ssn', 'nationalId']);
allow write: if request.auth != null && request.auth.uid == userId
&& request.resource.data.keys().hasAll(['displayName', 'avatarUrl'])
&& request.resource.data.diff(resource.data).affectedKeys().hasOnly(['displayName', 'avatarUrl']);
}
}
}
Third, for queries that return lists, apply server-side filtering and avoid exposing sensitive documents to unauthorized roles:
router.get('/api/profiles', async (req, res) => {
try {
const snapshot = await db.collection('profiles')
.where('public', '==', true)
.select('handle', 'status')
.get();
const results = [];
snapshot.forEach(doc => results.push({ id: doc.id, ...doc.data() }));
res.json(results);
} catch (err) {
res.status(500).json({ error: 'Internal server error' });
}
});
module.exports = router;
These examples illustrate how to align Express routes with secure Firestore usage: limit returned data, validate ownership, and constrain rule conditions. middleBrick can surface deviations from these patterns by scanning your endpoints and spec, highlighting where PII exposure is likely and providing remediation guidance tied to recognized frameworks.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |