Insufficient Logging in Express with Mongodb
Insufficient Logging in Express with Mongodb — how this specific combination creates or exposes the vulnerability
Insufficient logging in an Express application that uses Mongodb can weaken visibility into authentication failures, authorization decisions, and data access patterns. When request handling, database operations, and errors are not recorded with sufficient context, security-relevant events become harder to detect, correlate, and investigate.
Express itself does not enforce a logging standard, and Mongodb driver operations do not emit structured security logs by default. Without explicit instrumentation, you may miss indicators such as repeated authentication attempts, unexpected query filters, unauthorized access to sensitive documents, or malformed inputs that lead to injection or NoSQL injection attempts. For example, an unhandled error in a Mongodb callback might reveal stack traces or internal paths, while missing request identifiers prevent linking logs across services.
Consider an endpoint that updates a user profile using a user-supplied ID. If the route does not validate input, log the incoming identifier, the parsed query, and the outcome, an attacker can probe for IDOR or BOLA without leaving traces. A missing log entry for a failed findOneBy operation could indicate that an unauthorized lookup was silently ignored. Similarly, logging only successful queries while omitting rejected or filtered queries obscures privilege escalation attempts or unsafe consumption of untrusted data.
In a typical setup, developers may rely on Mongodb’s built-in profiling or rely on console statements that are not structured or centralized. This ad-hoc approach does not capture important metadata such as timestamps, request IDs, user context (when available), or the exact query and update objects sent to the database. Without these details, patterns like credential stuffing, brute-force attempts, or anomalous query structures are difficult to detect, especially when logs are rotated or retained for short periods.
The combination of Express routes and Mongodb operations amplifies this risk when logging is inconsistent across middleware, route handlers, and database layers. For instance, an Express error handler that omits request identifiers or sanitizes only part of the payload can leave blind spots. Middleware that does not log authorization decisions (e.g., whether a user is allowed to access a specific resource) can enable BOLA/IDOR to go unnoticed. Input validation failures that are not recorded may also hide injection attempts, including NoSQL injection via maliciously crafted objects passed to find or aggregate calls.
To align with security best practices, ensure that each request is assigned a stable identifier that is propagated through Express middleware and included in every Mongodb-related log entry. Record structured logs that capture the timestamp, route path, HTTP method, request identifier, user identifier (if authenticated), query or update payload (with sensitive data redacted), operation result (success/failure), and any validation or authorization outcomes. This approach supports detection of issues such as authentication bypass attempts, privilege escalation, unsafe consumption, and data exposure, and it complements compliance mappings to OWASP API Top 10 and related frameworks.
Mongodb-Specific Remediation in Express — concrete code fixes
Remediation focuses on structured, contextual logging at both the Express layer and the Mongodb interaction layer. Use a consistent request identifier, log key events and outcomes, and ensure sensitive fields are redacted. Centralize logging to simplify analysis and correlation.
Example Express setup with request ID propagation and Mongodb logging in JavaScript:
const express = require('express');
const { MongoClient } = require('mongodb');
const { v4: uuidv4 } = require('uuid');
const app = express();
app.use(express.json());
const uri = 'mongodb://localhost:27017';
const client = new MongoClient(uri);
// Middleware to assign and propagate requestId
app.use((req, res, next) => {
req.requestId = uuidv4();
res.setHeader('X-Request-ID', req.requestId);
next();
});
// Centralized logging helper
function logSecurityEvent({ level, requestId, path, method, userId, query, update, result, error, details = {} }) {
const entry = {
timestamp: new Date().toISOString(),
level,
requestId,
path,
method,
userId: userId || null,
query: query ? JSON.parse(JSON.stringify(query)) : null,
update: update ? JSON.parse(JSON.stringify(update)) : null,
result,
error: error ? String(error) : null,
...details
};
// Output as JSON line; pipe to your log collector
console.log(JSON.stringify(entry));
}
// Example route with logging at multiple points
app.post('/users/:id/profile', async (req, res) => {
const requestId = req.requestId;
const userId = req.user ? req.user.id : null;
const targetId = req.params.id;
const update = req.body;
logSecurityEvent({
level: 'info',
requestId,
path: req.path,
method: req.method,
userId,
query: { targetId },
update,
result: 'START',
details: { action: 'profile_update_initiated' }
});
try {
await client.connect();
const database = client.db('myapp');
const users = database.collection('users');
// Use parameterized filter to avoid NoSQL injection
const result = await users.updateOne(
{ _id: targetId, ownerId: userId }, // enforce ownership to mitigate BOLA
{ $set: update },
{ upsert: false }
);
if (result.matchedCount === 0) {
logSecurityEvent({
level: 'warn',
requestId,
path: req.path,
method: req.method,
userId,
query: { targetId },
update,
result: 'NO_MATCH',
details: { matched: 0, modified: result.modifiedCount }
});
} else {
logSecurityEvent({
level: 'info',
requestId,
path: req.path,
method: req.method,
userId,
query: { targetId },
update,
result: 'SUCCESS',
details: { matched: result.matchedCount, modified: result.modifiedCount }
});
}
res.status(200).json({ matched: result.matchedCount, modified: result.modifiedCount });
} catch (error) {
logSecurityEvent({
level: 'error',
requestId,
path: req.path,
method: req.method,
userId,
query: { targetId },
update,
result: 'FAIL',
error: error.message,
details: { name: error.name }
});
res.status(500).json({ error: 'Internal server error' });
} finally {
await client.close();
}
});
Key practices illustrated:
- Assign a request-scoped identifier in middleware and include it in every log line to correlate events across Express and Mongodb.
- Log both the intent (e.g., update profile) and the actual database operation, including the filter used, to detect unexpected query patterns.
- Enforce ownership checks in the query filter (e.g.,
ownerId: userId) to reduce BOLA risks and log whether the filter matched documents. - Redact sensitive fields before logging the query or update payloads; avoid logging full documents containing PII or secrets.
- Capture errors with stack metadata sparingly, ensuring error logs do not expose internal paths or code snippets that could aid an attacker.
- Structure logs as JSON to enable automated parsing and integration with SIEM or monitoring tools, improving detection of authentication anomalies, repeated failures, or injection attempts.
For continuous monitoring, consider integrating the CLI tool using middlebrick scan <url> to assess the unauthenticated attack surface and validate that logging practices do not inadvertently expose sensitive data. Teams using the Pro plan can enable continuous monitoring and CI/CD integration to enforce security thresholds in pipelines.