Prototype Pollution in Feathersjs
How Prototype Pollution Manifests in Feathersjs
Prototype Pollution in Feathersjs occurs when untrusted user input modifies JavaScript object prototypes, creating security vulnerabilities through the framework's data handling patterns. Feathersjs's flexible service architecture and dynamic query building make it particularly susceptible to prototype pollution attacks.
The most common attack vector involves Feathersjs's params.query object, which automatically parses URL query parameters into JavaScript objects. When users send parameters like __proto__.isAdmin=true, these properties get assigned to the prototype chain rather than the intended object, affecting all instances.
// Vulnerable Feathersjs service endpoint
app.get('/users', async (req, res) => {
const params = req.feathers || {};
// __proto__.isAdmin=true gets assigned to Object.prototype
// Now every object has isAdmin: true
const isAdmin = params.query?.isAdmin || false;
// This check fails because isAdmin is now true on all objects
if (!isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
const users = await userService.find(params);
return res.json(users);
});Another critical pattern involves Feathersjs's patch and update operations. The framework's data validation occurs after prototype pollution has already compromised the object structure:
// Vulnerable patch operation
app.patch('/users/:id', async (req, res) => {
const { id } = req.params;
const data = req.feathers?.data || {};
// If data contains __proto__.role='admin', it affects all users
await userService.patch(id, data, req.feathers);
return res.json({ success: true });
});Feathersjs's hook system can also propagate prototype pollution through context.params. Hooks often merge user input without proper sanitization:
// Vulnerable hook
const vulnerableHook = async context => {
// Merges user input directly into params
context.params = { ...context.params, ...context.data };
return context;
};The framework's MongoDB adapter is particularly vulnerable because it directly uses query objects for database operations. Prototype pollution can bypass authorization checks by adding properties to the prototype chain that affect query results globally.
Feathersjs-Specific Detection
Detecting Prototype Pollution in Feathersjs requires examining both runtime behavior and static code patterns. The most effective approach combines automated scanning with manual code review.
middleBrick's API security scanner includes specialized detection for Prototype Pollution patterns in Feathersjs applications. The scanner examines request handling patterns, hook implementations, and data validation logic to identify vulnerable code paths.
# Scan a Feathersjs API endpoint
middlebrick scan https://api.example.com/users
# Results show Prototype Pollution findings with severity and
# specific code locations where the vulnerability exists
Manual detection should focus on these Feathersjs-specific patterns:
- Direct assignment of
req.feathers.datawithout validation - Use of
Object.assign()or spread operators on user input - Hook chains that merge
context.paramswith untrusted data - Query parameter parsing without prototype pollution protection
Static analysis tools can identify risky patterns in Feathersjs codebases:
// Dangerous patterns to flag
const dangerousPatterns = [
'Object.assign(context.params, user_input)',
'{ ...context.params, ...user_input }',
'req.feathers.data',
'params.query.__proto__'
];Runtime detection involves logging and monitoring for unusual object property assignments. Feathersjs applications should log prototype modifications and alert on suspicious patterns.
Feathersjs-Specific Remediation
Remediating Prototype Pollution in Feathersjs requires a defense-in-depth approach using the framework's built-in security features and careful input handling.
The most effective protection is using Object.create(null) for objects that will contain user input:
// Secure pattern for query parameters
app.get('/users', async (req, res) => {
const safeParams = Object.create(null);
// Only allow specific properties
const allowed = ['page', 'limit', 'search'];
for (const key of allowed) {
if (req.query[key] !== undefined) {
safeParams[key] = req.query[key];
}
}
const users = await userService.find({ query: safeParams });
return res.json(users);
});Feathersjs's hook system provides excellent protection points. Create a prototype pollution prevention hook:
// Prototype pollution prevention hook
const preventPrototypePollution = async context => {
const sanitize = obj => {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) {
return obj.map(sanitize);
}
const cleaned = Object.create(null);
for (const [key, value] of Object.entries(obj)) {
if (key.startsWith('__proto__') || key.includes('.__proto__')) {
throw new Error('Prototype pollution attempt detected');
}
cleaned[key] = sanitize(value);
}
return cleaned;
};
context.data = sanitize(context.data);
context.params.query = sanitize(context.params.query);
return context;
};
// Apply globally
app.hooks({
before: {
all: [preventPrototypePollution]
}
});For Feathersjs services, use the whitelist option to restrict allowed properties:
// Secure service definition
app.service('users').hooks({
before: {
create: [sanitizeData(['name', 'email', 'role'])],
patch: [sanitizeData(['name', 'email'])],
update: [sanitizeData(['name', 'email', 'role'])]
}
});Implement comprehensive input validation using libraries like joi or zod with strict schemas:
import { z } from 'zod';
const userSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(['user', 'admin']).optional()
});
const validateInput = async context => {
try {
context.data = userSchema.parse(context.data);
} catch (error) {
throw new Error('Invalid input data');
}
return context;
};Enable prototype pollution protection in your Feathersjs configuration by setting Object.freeze(Object.prototype) during application startup, though this may affect third-party libraries.