Prototype Pollution in Express with Api Keys
Prototype Pollution in Express with Api Keys — how this specific combination creates or exposes the vulnerability
Prototype pollution in Express applications that rely on Api Keys often arises when user-controlled input modifies the prototype of JavaScript objects used for configuration, authorization, or request handling. In Express, middleware commonly parses JSON payloads and merges them into configuration or parameter objects. If the application directly mutates objects such as req.query, req.body, or custom configuration objects using user input, an attacker can provide properties like __proto__, constructor.prototype, or other special keys that alter the behavior of inherited properties across objects.
When Api Keys are involved, these polluted prototypes can affect how key validation or extraction logic behaves. For example, an Api Key might be read from req.headers['x-api-key'] and then compared against a set of allowed keys stored in a shared object. If that shared object is polluted, an attacker may be able to modify its properties—such as adding or changing key values—leading to unauthorized access. This can happen when the application uses global or module-level objects to store routing rules, key scopes, or rate-limiting state, and those objects are unintentionally modified through deeply nested or malicious input.
Consider an Express route that merges query parameters into a configuration object used for access control:
const allowedKeys = { 'prod-key-123': { scope: 'read' } };
app.get('/data', (req, res) => {
const params = { ...allowedKeys, ...req.query };
if (params['x-api-key'] !== 'prod-key-123') {
return res.status(401).send('Unauthorized');
}
res.json({ data: 'sensitive' });
});
If an attacker sends a request with ?__proto__[scope]=admin, the allowedKeys-derived object may inherit the polluted prototype, potentially affecting equality checks elsewhere in the application. While this specific example does not directly bypass the hardcoded key, in more complex setups where key validation logic depends on inherited properties or shared mutable state, prototype pollution can lead to privilege escalation or incorrect authorization decisions.
The risk is compounded when the application uses third-party libraries that rely on prototypes or when logging and error-handling routines serialize polluted objects. Sensitive Api Key validation logic may then reference unexpected properties, bypassing intended controls. Because middleBrick scans for such input validation and property authorization issues across 12 parallel checks—covering BFLA/Privilege Escalation, Input Validation, and Property Authorization—it can surface these patterns in unauthenticated scans, providing prioritized findings with severity and remediation guidance.
Api Keys-Specific Remediation in Express — concrete code fixes
To secure Express applications handling Api Keys, avoid mutating shared or inherited objects with user input. Use isolated data structures, validate keys against a trusted source, and sanitize all incoming payloads. Below are concrete, secure code examples that demonstrate proper handling.
1. Validate Api Keys Without Mutating Prototypes
Read the Api Key from headers and compare it against a static, non-extensible set of valid keys. Do not merge user input into objects that influence authorization logic.
const validKeys = new Set(['abc123', 'def456', 'prod-key-123']);
app.get('/secure', (req, res) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || !validKeys.has(apiKey)) {
return res.status(401).json({ error: 'Invalid Api Key' });
}
res.json({ message: 'Authorized' });
});
2. Use Immutable Configuration and Deep Copy Safely
If you must merge configuration with request parameters, ensure the target object is not linked to prototypes used elsewhere. Use JSON.parse(JSON.stringify()) or a safe deep-copy method to isolate the base configuration, and avoid spreading user input into shared objects.
const baseConfig = { rateLimit: 100, scopes: ['read'] };
app.post('/configure', express.json(), (req, res) => {
// Safe: create a clean copy without prototype pollution risk
const config = JSON.parse(JSON.stringify(baseConfig));
// Validate and apply only expected fields
if (typeof req.body.rateLimit === 'number' && req.body.rateLimit > 0) {
config.rateLimit = req.body.rateLimit;
}
// Never merge user input directly into shared or inherited objects
res.json({ config });
});
3. Enforce Strict Key Extraction and Reject Dangerous Properties
Explicitly extract known-safe fields and reject inputs containing __proto__, constructor, or other dangerous keys. This prevents accidental pollution even if the middleware or library uses object iteration.
app.use((req, res, next) => {
const body = req.body;
if (body) {
for (const key of Object.keys(body)) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
return res.status(400).json({ error: 'Invalid property in request' });
}
}
}
next();
});
app.post('/keys', express.json(), (req, res) => {
const { apiKey } = req.body;
if (!apiKey || typeof apiKey !== 'string') {
return res.status(400).json({ error: 'Missing or invalid apiKey' });
}
// Proceed with validation
res.json({ receivedKey: apiKey });
});
These practices reduce the attack surface around Api Key handling. middleBrick’s checks for Input Validation, Property Authorization, and BFLA/Privilege Escalation help identify areas where user-controlled data interacts with sensitive logic, supporting compliance with OWASP API Top 10 and related frameworks.