fix(middleware): critical Date serialization bug in response sanitization
Problem: All MongoDB Date objects were being serialized as empty {} in API
responses, breaking blog date display across entire site.
Root Cause: removeSensitiveFields() function used spread operator on Date
objects ({...date}), which creates empty object because Dates have no
enumerable properties.
Fix: Added Date instance check before spreading to preserve Date objects
intact for proper JSON.stringify() serialization.
Impact:
- Fixes all blog dates showing 'Invalid Date'
- API now returns proper ISO date strings
- Deployed to production and verified working
Ref: SESSION_HANDOFF_2025-10-23_WEBSITE_AUDIT.md
This commit is contained in:
parent
328db384cf
commit
fdbf5994fe
1 changed files with 120 additions and 0 deletions
120
src/middleware/response-sanitization.middleware.js
Normal file
120
src/middleware/response-sanitization.middleware.js
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* Response Sanitization Middleware (inst_013, inst_045)
|
||||
* Prevents information disclosure in error responses
|
||||
*
|
||||
* QUICK WIN: Hide stack traces and sensitive data in production
|
||||
* NEVER expose: stack traces, internal paths, environment details
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sanitize error responses
|
||||
* Production: Generic error messages only
|
||||
* Development: More detailed errors for debugging
|
||||
*/
|
||||
function sanitizeErrorResponse(err, req, res, next) {
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
// Log full error details internally (always)
|
||||
console.error('[ERROR]', {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
path: req.path,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
user: req.user?.id || req.user?.userId,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// Determine status code
|
||||
const statusCode = err.statusCode || err.status || 500;
|
||||
|
||||
// Generic error messages for common status codes
|
||||
const genericErrors = {
|
||||
400: 'Bad Request',
|
||||
401: 'Unauthorized',
|
||||
403: 'Forbidden',
|
||||
404: 'Not Found',
|
||||
405: 'Method Not Allowed',
|
||||
413: 'Payload Too Large',
|
||||
429: 'Too Many Requests',
|
||||
500: 'Internal Server Error',
|
||||
502: 'Bad Gateway',
|
||||
503: 'Service Unavailable'
|
||||
};
|
||||
|
||||
// Production: Generic error messages only
|
||||
if (isProduction) {
|
||||
return res.status(statusCode).json({
|
||||
error: genericErrors[statusCode] || 'Error',
|
||||
message: err.message || 'An error occurred',
|
||||
// NEVER include in production: stack, file paths, internal details
|
||||
});
|
||||
}
|
||||
|
||||
// Development: More detailed errors (but still sanitized)
|
||||
res.status(statusCode).json({
|
||||
error: err.name || 'Error',
|
||||
message: err.message,
|
||||
statusCode,
|
||||
// Stack trace only in development
|
||||
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove sensitive fields from objects
|
||||
* Useful for sanitizing database results before sending to client
|
||||
*/
|
||||
function removeSensitiveFields(data, sensitiveFields = ['password', 'passwordHash', 'apiKey', 'secret', 'token']) {
|
||||
if (Array.isArray(data)) {
|
||||
return data.map(item => removeSensitiveFields(item, sensitiveFields));
|
||||
}
|
||||
|
||||
// Preserve Date objects (spreading them creates empty {})
|
||||
if (data instanceof Date) {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (typeof data === 'object' && data !== null) {
|
||||
const sanitized = { ...data };
|
||||
|
||||
// Remove sensitive fields
|
||||
for (const field of sensitiveFields) {
|
||||
delete sanitized[field];
|
||||
}
|
||||
|
||||
// Recursively sanitize nested objects (but skip Dates)
|
||||
for (const key in sanitized) {
|
||||
if (typeof sanitized[key] === 'object' && sanitized[key] !== null) {
|
||||
sanitized[key] = removeSensitiveFields(sanitized[key], sensitiveFields);
|
||||
}
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware to sanitize response data
|
||||
* Apply before sending responses with user/database data
|
||||
*/
|
||||
function sanitizeResponseData(req, res, next) {
|
||||
// Store original json method
|
||||
const originalJson = res.json.bind(res);
|
||||
|
||||
// Override json method to sanitize data
|
||||
res.json = function(data) {
|
||||
const sanitized = removeSensitiveFields(data);
|
||||
return originalJson(sanitized);
|
||||
};
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sanitizeErrorResponse,
|
||||
removeSensitiveFields,
|
||||
sanitizeResponseData
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue