Excessive Data Exposure in Express
How Excessive Data Exposure Manifests in Express
Excessive Data Exposure in Express applications often stems from developers exposing entire database records without considering what data should be publicly accessible. This vulnerability manifests in several Express-specific patterns.
The most common scenario occurs in route handlers that directly send database query results to clients. Consider a typical Express route that retrieves user data:
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
res.json(user);
});
This pattern exposes sensitive fields like password hashes, internal IDs, timestamps, and potentially PII that should never leave the server. Express's res.json() method makes it trivial to accidentally send entire objects.
Another Express-specific manifestation occurs with Mongoose population. Developers often populate related documents without filtering:
app.get('/api/orders/:id', async (req, res) => {
const order = await Order.findById(req.params.id)
.populate('customer')
.populate('items');
res.json(order);
});
If the Customer model contains sensitive fields like ssn, creditScore, or internalNotes, these get exposed through population. The populate() method in Mongoose doesn't automatically filter sensitive fields.
Express middleware chains can also propagate sensitive data. A middleware that attaches user data to req.user might include fields that downstream handlers shouldn't expose:
async function authenticateUser(req, res, next) {
const token = req.headers.authorization;
const user = await User.findByToken(token);
req.user = user; // Contains password hash, internal flags
next();
}
app.get('/profile', (req, res) => {
res.json(req.user); // Exposes sensitive fields from middleware
});
Express error handling provides another attack vector. Error objects often contain stack traces, database credentials, or internal server paths:
app.use((err, req, res, next) => {
res.status(500).json({ error: err.message, stack: err.stack });
});
In production, stack traces reveal file paths, database queries, and internal logic that attackers can exploit.
Express-Specific Detection
Detecting excessive data exposure in Express requires both static analysis and runtime scanning. middleBrick's Express-specific detection examines your API endpoints for data exposure patterns without requiring source code access.
middleBrick scans the unauthenticated attack surface by sending requests to your Express endpoints and analyzing responses. For a typical Express application, it identifies:
- Unexpected fields in JSON responses (password hashes, internal IDs, timestamps)
- Database error messages containing query details or table names
- Stack traces in error responses
- Full object serialization without field filtering
- Population of sensitive related documents
The scanner tests endpoints with various inputs to trigger different response paths, including error conditions that might expose internal data. For example, requesting a non-existent resource might return:
{
"error": "Cast to ObjectId failed for value \"invalid\" at path \"_id\"",
"stack": "TypeError: Cannot read property 'name' of null..."
}
This reveals internal implementation details like MongoDB ObjectIds and model structure.
middleBrick's OpenAPI/Swagger analysis complements runtime scanning by examining your API specification. It cross-references documented response schemas with actual runtime responses to identify discrepancies where more data is returned than documented.
For Express applications using middleware, middleBrick analyzes the request-response cycle to detect data leakage through middleware chains. It examines how data flows from authentication middleware through route handlers to the final response.
The CLI tool provides Express developers with quick scanning capabilities:
middlebrick scan http://localhost:3000/api/users
This scans all exposed endpoints on your Express server, providing a security score and detailed findings about data exposure issues.
Express-Specific Remediation
Remediating excessive data exposure in Express requires a multi-layered approach using Express's native features and established patterns.
The most effective approach is explicit field selection when querying databases. For Mongoose, use .select() to specify exactly which fields to return:
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id)
.select('name email role createdAt')
.lean();
res.json(user);
});
The .lean() method converts Mongoose documents to plain objects, preventing accidental exposure of Mongoose-specific properties.
For population scenarios, use field selection on populated documents:
app.get('/api/orders/:id', async (req, res) => {
const order = await Order.findById(req.params.id)
.populate('customer', 'name email -_id')
.populate('items', 'name price quantity -_id');
res.json(order);
});
The minus sign (-) excludes fields, while _id is often unnecessary in API responses.
Express middleware can sanitize data before it reaches route handlers. Create a sanitization middleware:
function sanitizeUserData(req, res, next) {
if (req.user) {
const { password, __v, ...safeUser } = req.user.toObject();
req.user = safeUser;
}
next();
}
app.use(authenticateUser);
app.use(sanitizeUserData);
This removes sensitive fields from req.user before any route handler accesses it.
For error handling, implement Express error middleware that never exposes internal details:
app.use((err, req, res, next) => {
console.error(err); // Log full error server-side
res.status(500).json({
error: process.env.NODE_ENV === 'development'
? err.message
: 'Internal server error'
});
});
In production, only generic error messages are sent to clients.
Response transformation middleware can filter outgoing data:
function filterResponse(filters) {
return (req, res, next) => {
const originalSend = res.json;
res.json = (data) => {
const filtered = filters.reduce((obj, filter) => {
if (obj[filter] !== undefined) {
const { [filter]: removed, ...rest } = obj;
return rest;
}
return obj;
}, data);
originalSend.call(res, filtered);
};
next();
};
}
app.use(filterResponse(['password', 'ssn', 'creditScore']));
This automatically removes specified fields from all JSON responses.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |