Insufficient Logging in Express with Firestore
Insufficient Logging in Express with Firestore — how this specific combination creates or exposes the vulnerability
Insufficient logging in an Express application that uses Google Cloud Firestore can leave critical security events unrecorded, complicating detection, investigation, and compliance. Because Firestore is a managed NoSQL datastore, logging gaps differ from traditional SQL setups: you must capture both HTTP-layer signals and Firestore-specific metadata to build an auditable trail.
At a high level, insufficient logging manifests in three dimensions when Express talks to Firestore:
- Authentication and identity: missing or opaque user and service-account context for each Firestore operation.
- Authorization and intent: lack of clear records for access decisions (e.g., why a read or write was allowed or denied) and for rule evaluations that occur on Firestore side.
- Data and operations: incomplete capture of document paths, query shapes, mutation payloads (redacted appropriately), timestamps, and request IDs needed to trace a transaction end to end.
In practice, an Express route that performs Firestore reads/writes without structured logs might record only a generic success/failure status. This makes it difficult to answer questions like: Which identity attempted this operation? What document ID was targeted? Were Firestore security rules evaluated per request? Without these details, anomalies such as unusual query patterns, privilege escalation attempts (BOLA/IDOR), or unsafe consumption of administrative endpoints remain invisible or are noticed only after an incident.
For compliance mappings, insufficient logging weakens evidence for frameworks such as OWASP API Top 10 (API4:2023 Broken Object Level Authorization), PCI-DSS (requirement 10), SOC 2 (CC6.1/CC7.1), and GDPR (accountability and breach detection). middleBrick scans highlight these logging gaps as part of its Data Exposure and Authentication checks, providing prioritized findings and remediation guidance rather than attempting to fix the logs automatically.
Concrete risk scenarios include:
- An unlogged call to
doc.get()in an admin route means a tampered request that reads other users’ data (IDOR) leaves no trace. - Missing structured metadata for batch writes can obscure which client payload triggered an unexpected document update.
- No request correlation IDs across Express middleware and Firestore operations prevent stitching together a full timeline for incident forensics.
To mitigate, design logs that are consistent across the Express layer and Firestore interactions: include a stable request ID, user/service identity (subject), HTTP method and route, Firestore path, operation type, outcome (success/denied), and a redacted summary of the document mutation. Ensure logs are centralized and retained according to your compliance requirements. middleBrick’s dashboard can track your security scores over time and surface insufficient logging findings, while the CLI (middlebrick scan <url>) produces structured output suitable for integration into scripts and CI/CD gates.
Firestore-Specific Remediation in Express — concrete code fixes
Remediation focuses on adding structured, actionable logs around Firestore usage in Express, without changing runtime behavior. Use request-scoped context (e.g., a unique request ID propagated through middleware) and Firestore metadata (path, collection, query constraints, and outcome) to create logs that support security investigations.
Example: Express middleware that enriches requests with an ID and identity, followed by a route that reads and writes Firestore with comprehensive logging.
// logger.js import { v4 as uuidv4 } from 'uuid'; import { format } from 'date-fns'; export function requestId(req, res, next) { req.id = req.headers['x-request-id'] || uuidv4(); res.setHeader('X-Request-Id', req.id); next(); } export function auditLog(level, message, metadata = {}) { const entry = { timestamp: new Date().toISOString(), level, message, ...metadata, }; // Replace with your transport: console, Winston, Bunyan, or external SIEM console[level](JSON.stringify(entry)); } // server.js import express from 'express'; import { initializeApp } from 'firebase-admin/app'; import { getFirestore, doc, getDoc, setDoc } from 'firebase-admin/firestore'; import { requestId, auditLog } from './logger.js'; initializeApp(); const db = getFirestore(); const app = express(); app.use(express.json()); app.use(requestId); app.get('/users/:uid', async (req, res) => { const { uid } = req.params; const path = `users/${uid}`; const requestId = req.id; const subject = req.get('x-subject') || 'anonymous'; try { const docSnap = await db.doc(path).get(); if (!docSnap.exists) { auditLog('warn', 'Firestore read: document not found', { requestId, subject, method: req.method, path: req.path, firestorePath: path, operation: 'get', outcome: 'not_found', }); return res.status(404).json({ error: 'not_found' }); } auditLog('info', 'Firestore read: success', { requestId, subject, method: req.method, path: req.path, firestorePath: path, operation: 'get', outcome: 'success', dataKeys: Object.keys(docSnap.data() || {}), }); res.json({ id: docSnap.id, ...docSnap.data() }); } catch (err) { auditLog('error', 'Firestore read: failure', { requestId, subject, method: req.method, path: req.path, firestorePath: path, operation: 'get', outcome: 'error', error: err.message, }); res.status(500).json({ error: 'internal' }); } }); app.post('/users/:uid/profile', async (req, res) => { const { uid } = req.params; const path = `users/${uid}/profile`; const requestId = req.id; const subject = req.get('x-subject') || 'anonymous'; const payload = req.body; try { // Redact sensitive keys before logging const redacted = { ...payload }; if (redacted.password) redacted.password = '**REDACTED**'; if (redacted.ssn) redacted.ssn = '**REDACTED**'; await setDoc(doc(path), redacted, { merge: true }); auditLog('info', 'Firestore write: success', { requestId, subject, method: req.method, path: req.path, firestorePath: path, operation: 'set', outcome: 'success', mutation: redacted, }); res.json({ id: uid, profile: redacted }); } catch (err) { auditLog('error', 'Firestore write: failure', { requestId, subject, method: req.method, path: req.path, firestorePath: path, operation: 'set', outcome: 'error', error: err.message, }); res.status(500).json({ error: 'internal' }); } }); app.listen(3000, () => console.log('API listening on 3000'));Key points in this remediation:
- Include request ID and subject (identity) in every log line to correlate Express middleware and Firestore operations.
- Log operation type, Firestore path, and outcome (success/not_found/error) to support traceability and alerting.
- Redact sensitive fields in mutation payloads before logging to avoid credential or PII exposure in logs.
- Use structured logging (e.g., JSON) to enable indexing and querying by security tools.
middleBrick’s scans can surface insufficient logging findings and map them to relevant compliance controls. The Pro plan enables continuous monitoring so future changes to Firestore usage can trigger alerts if logging completeness degrades. Use the CLI (middlebrick scan <url>) to validate endpoint behavior and the GitHub Action to enforce security gates in CI/CD.