Mass Assignment in Feathersjs with Basic Auth
Mass Assignment in Feathersjs with Basic Auth — how this specific combination creates or exposes the vulnerability
Mass assignment occurs when a framework binds incoming request properties directly to a model or database record without explicit allowlisting. In Feathersjs, services often rely on hooks such as before: { create, update } to transform payloads. When Basic Auth is used for authentication, the request typically carries an Authorization: Basic base64(credentials) header and the service may treat the authenticated identity as a trusted context. This combination can inadvertently expose mass assignment risks: if a hook merges params (which includes the authenticated user object and query/body) into the data sent to the Model layer, and the hook does not explicitly remove or restrict writable fields, an attacker who knows or guesses a valid username/password can modify sensitive properties they should not control, such as isAdmin, role, or permissions.
Consider a typical Feathersjs service definition without proper property authorization:
const { Service, Model } = require('feathers-sequelize');
class TicketService extends Service {
async create(data, params) {
// Risk: data may contain fields like isAdmin, role, or permissions
return super.create(data, params);
}
}
module.exports = function () {
this.use('/tickets', new TicketService({ Model, paginate: { default: 10 } }));
};
If the service is protected by Basic Auth and the hook chain does not filter payload properties, an authenticated user could submit { title: 'Fix bug', isAdmin: true } and, depending on hook configuration, have the isAdmin field persisted. This maps to the OWASP API Top 10 BOLA/IDOR and Property Authorization categories because the issue is about improper authorization of properties relative to the authenticated subject.
Feathersjs itself does not introduce mass assignment; the risk arises when developers omit property filtering in hooks or when using generic object spreading that copies all incoming fields. With Basic Auth, the authenticated identity is often attached to params.account or a similar property, and hooks may unintentionally merge identity information with request data. The scanner category BOLA/IDOR and Property Authorization would flag such endpoints if runtime tests detect that unauthenticated or lower-privilege contexts can modify elevated fields.
Remediation guidance centers on explicit allowlisting and avoiding blind passthrough of payloads. Use hooks to pick only safe fields, and validate that sensitive fields are not present or are overridden server-side regardless of the authenticated identity.
Basic Auth-Specific Remediation in Feathersjs — concrete code fixes
To mitigate mass assignment with Basic Auth in Feathersjs, enforce strict property filtering in service hooks and avoid merging raw payload into model data. Below are concrete, working examples demonstrating secure patterns.
1. Basic Auth setup in Feathersjs
First, configure authentication using @feathersjs/authentication and @feathersjs/authentication-local. This attaches the authenticated user to params.account.
const authentication = require('@feathersjs/authentication');
const local = require('@feathsjs/authentication-local');
app.configure(authentication({
entity: 'user',
service: 'users',
secret: 'your-secret',
authStrategies: ['local']
}));
app.configure(local());
2. Explicit property allowlist in a create hook
Define a hook that copies only permitted fields from data before passing it to the service. This prevents any extra fields supplied by the client from being stored.
// hooks/ticket-hooks.js
const allowedFields = ['title', 'description', 'status'];
function sanitizeTicketData(context) {
if (context.data && typeof context.data === 'object') {
const filtered = {};
allowedFields.forEach(field => {
if (Object.prototype.hasOwnProperty.call(context.data, field)) {
filtered[field] = context.data[field];
}
});
context.data = filtered;
}
return context;
}
module.exports = {
before: {
create: [sanitizeTicketData],
update: [sanitizeTicketData]
}
};
Apply the hook when registering the service:
const TicketService = require('./ticket-service');
const ticketHooks = require('./hooks/ticket-hooks');
app.use('/tickets', new TicketService({ Model, paginate: { default: 10 } }));
app.service('/tickets').hooks(ticketHooks);
3. Server-side override of sensitive fields
Even if a malicious field slips through, ensure sensitive attributes are derived from server-side identity, not from the payload. For example, do not allow clients to set role or isAdmin.
function removeSensitiveFields(context) {
delete context.data.isAdmin;
delete context.data.role;
delete context.data.permissions;
return context;
}
// Combine with authentication so params.account holds the user
app.service('tickets').hooks({
before: {
create: [removeSensitiveFields],
update: [removeSensitiveFields]
}
});
4. Using a validation layer with an allowlist
For more complex schemas, use a validation library (e.g., Ajv) to enforce an allowlist strictly. This example demonstrates a lightweight approach without external dependencies:
function strictAllowlist(context) {
const base = { title: 'string', description: 'string' };
const allowed = Object.keys(base);
if (context.data && typeof context.data === 'object') {
const filtered = {};
allowed.forEach(k => { if (context.data[k] !== undefined) filtered[k] = context.data[k]; });
context.data = filtered;
}
return context;
}
By combining Basic Auth with deliberate property filtering, you reduce the risk of mass assignment. The authenticated identity in params.account should only be used for ownership or visibility rules, never as a justification to trust client-supplied fields.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |