CWE-209 in Express
How Cwe 209 Manifests in Express
CWE-209: Generation of Error Message Containing Sensitive Information is particularly problematic in Express applications because error handling is often implemented inconsistently across routes and middleware. When Express encounters an error, it typically propagates it through the error-handling middleware chain, and developers frequently expose stack traces or internal implementation details in responses.
Common Express-specific manifestations include:
- Uncaught exceptions in route handlers that bubble up to Express's default error handler, which sends stack traces to clients
- Database query failures that expose connection strings, table names, or SQL syntax
- Authentication failures that reveal whether usernames exist in the system
- Configuration errors that expose environment variables or file paths
- Third-party API failures that leak service endpoints or API keys
Consider this vulnerable Express route:
app.post('/api/users', async (req, res) => {
try {
const user = await User.create(req.body);
res.json(user);
} catch (err) {
res.status(500).json({ error: err.message });
}
});If the database connection fails, the client receives: { "error": "Failed to connect to MySQL: Access denied for user 'admin'@'localhost' (using password: YES)" } — exposing credentials and database type.
Another Express-specific scenario involves error-handling middleware:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: err.message });
});This pattern, while common, sends stack traces to the client if err.message contains newline characters or if the error object structure is exposed.
Express-Specific Detection
Detecting CWE-209 in Express applications requires examining both code patterns and runtime behavior. middleBrick's Express-specific scanning includes:
- Pattern matching for common error-handling anti-patterns in route definitions
- Analysis of error middleware that may expose stack traces or internal details
- Testing error responses across all endpoints to identify sensitive data leakage
- Checking for inconsistent error handling across different routes
Manual detection techniques for Express applications:
# Search for vulnerable patterns
grep -r "res.status(500).json" routes/ --include="*.js"
grep -r "catch (err)" routes/ --include="*.js"
grep -r "throw new Error" routes/ --include="*.js"
# Check for missing error handling
find routes/ -name "*.js" -exec grep -L "try" {} \;middleBrick's automated approach tests actual API responses:
$ middlebrick scan https://api.example.com
✅ Authentication Check
✅ BOLA/IDOR Check
✅ BFLA Check
✅ Property Authorization
✅ Input Validation
✅ Rate Limiting
✅ Data Exposure
✅ Encryption
✅ SSRF
✅ Inventory Management
✅ Unsafe Consumption
✅ LLM/AI Security
Security Score: C (72/100)
Critical Finding: Error Message Information Disclosure
Details:
- Endpoint: POST /api/users
- Issue: Database error messages expose MySQL credentials
- Severity: High
- Recommendation: Implement centralized error handling with sanitized responsesmiddleBrick specifically tests Express applications by:
- Triggering error conditions to observe response content
- Analyzing error middleware implementations
- Checking for stack trace exposure in development vs production
- Verifying consistent error response formats
Express-Specific Remediation
Express provides several native patterns for secure error handling. The most effective approach is centralized error handling with sanitized responses:
// Centralized error handler
app.use((err, req, res, next) => {
// Log the full error for developers
console.error('Error:', {
message: err.message,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
timestamp: new Date().toISOString(),
requestId: req.id
});
// Send sanitized response to clients
const statusCode = err.statusCode || 500;
const message = statusCode === 500 ? 'Internal Server Error' : err.message;
res.status(statusCode).json({
error: message,
requestId: req.id, // Optional: correlation ID for debugging
timestamp: new Date().toISOString()
});
});Route-specific error handling with custom error classes:
class DatabaseError extends Error {
constructor(message, originalError) {
super(message);
this.name = 'DatabaseError';
this.statusCode = 500;
this.originalError = originalError;
}
}
app.post('/api/users', async (req, res, next) => {
try {
const user = await User.create(req.body);
res.json(user);
} catch (err) {
if (err.name === 'SequelizeUniqueConstraintError') {
return next(new ValidationError('User already exists'));
}
return next(new DatabaseError('Failed to create user', err));
}
});Using Express error middleware with four parameters:
// Error middleware MUST have 4 parameters
app.use((err, req, res, next) => {
// This will only be called for errors
// NOT for normal requests
res.status(err.status || 500).json({
error: err.expose ? err.message : 'Internal Server Error'
});
});Production-ready error handling with environment-specific behavior:
const isProduction = process.env.NODE_ENV === 'production';
function createErrorHandler() {
return (err, req, res, next) => {
const errorInfo = {
message: err.message,
timestamp: new Date().toISOString(),
path: req.path,
method: req.method,
ip: req.ip
};
n if (!isProduction) {
errorInfo.stack = err.stack;
errorInfo.query = req.query;
errorInfo.body = req.body;
}
console.error('API Error:', errorInfo);
const response = {
error: isProduction ? 'Internal Server Error' : err.message,
requestId: req.id
};
res.status(err.status || 500).json(response);
};
}
app.use(createErrorHandler());