Mass Assignment in Feathersjs with Cockroachdb
Mass Assignment in Feathersjs with Cockroachdb — how this specific combination creates or exposes the vulnerability
Mass assignment in a Feathersjs service backed by Cockroachdb occurs when a service method accepts user-supplied data that maps directly to database columns without explicit allowlisting. Feathersjs is a framework that favors flexibility: by default, a service created with feathers generate service will accept a payload object and forward it to the database adapter with minimal filtering. When the adapter is configured for Cockroachdb, the ORM or query builder typically builds SQL from the keys present in that object. If the service does not sanitize incoming fields, an attacker can supply properties that map to sensitive or computed columns, unintentionally modifying fields such as is_admin, role, tenant_id, or version columns used for optimistic concurrency control.
In a Feathersjs app using an ORM like Sequelize with a Cockroachdb dialect, models define a fields or allowFields configuration for safe population, but if the service method does not enforce this allowlist, the raw payload is passed through. Cockroachdb, being PostgreSQL-wire compatible, does not inherently restrict which columns can be set by an INSERT or UPDATE from the application layer; that restriction is enforced by the application or the ORM. A common pattern is to use a DTO (data transfer object) and then merge it with the database model. If the merge is unconditional, fields such as created_at, updated_at, or even foreign key references can be overwritten. Additionally, Feathers hooks that run before validation may transform data in ways that still preserve unexpected fields, especially when using generic hooks like hooks.common() without a strict schema validation layer.
Consider an endpoint /users that accepts profile updates. A developer might assume that only known fields like name and email are mutable, but without an explicit schema check, a payload including {"email": "[email protected]", "is_admin": true} could be processed if the service does not filter is_admin. Because Cockroachdb stores this data in a distributed SQL table, the update propagates across nodes, and the privilege escalation persists. The vulnerability is not in Cockroachdb itself but in how the Feathers service composes and transmits SQL statements based on unchecked input. This maps to the broader BOLA/IDOR and Property Authorization checks in middleBrick’s 12 security checks, where unchecked object properties can lead to privilege escalation.
Real-world attack patterns include exploiting mass assignment to change ownership fields (e.g., tenant_id) to escalate across multi-tenant schemas, or toggling boolean flags that gate administrative access. In regulated contexts, such issues can intersect with compliance frameworks like OWASP API Top 10 (A01:2023) and GDPR data integrity requirements. Because Feathersjs emphasizes rapid prototyping, developers may overlook strict input validation, especially when integrating with Cockroachdb’s rich SQL feature set, assuming the database will enforce constraints. However, constraints like column-level defaults or check constraints are not a substitute for application-layer allowlisting.
Cockroachdb-Specific Remediation in Feathersjs — concrete code fixes
To remediate mass assignment in a Feathersjs service with Cockroachdb, explicitly define an allowlist of fields that are safe to assign and apply it before the data reaches the database adapter. Use Feathers hooks to validate and sanitize payloads rather than relying on model defaults or ORM behavior. Below are concrete code examples using Sequelize as the ORM with a Cockroachdb dialect.
First, define a model with explicit fields and avoid exposing sensitive columns in default scopes:
// src/models/user.model.js
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
email: { type: DataTypes.STRING, allowNull: false, unique: true },
name: { type: DataTypes.STRING },
is_admin: { type: DataTypes.BOOLEAN, defaultValue: false },
tenant_id: { type: DataTypes.UUID, allowNull: false }
}, {
// Avoid returning sensitive columns by default
defaultScope: {
attributes: { exclude: ['is_admin'] }
},
scopes: {
withSensitive: { attributes: {} }
}
});
return User;
};
Next, implement a before hook in your Feathers service that purges disallowed fields. Use a schema validator like Ajv or a simple whitelist approach:
// src/hooks/allowlist-hook.js
const ALLOWED_USER_FIELDS = ['name', 'email'];
function allowlistHook(context) {
if (context.data && typeof context.data === 'object') {
const filtered = {};
for (const key of ALLOWED_USER_FIELDS) {
if (Object.prototype.hasOwnProperty.call(context.data, key)) {
filtered[key] = context.data[key];
}
}
context.data = filtered;
}
return context;
}
module.exports = { allowlistHook };
Then, apply the hook in your service definition:
// src/services/users/users.service.js
const { allowlistHook } = require('./hooks/allowlist-hook');
const { v4: uuidv4 } = require('uuid');
module.exports = function (app) {
const options = {
name: 'users',
paginate: { default: 10, max: 50 }
};
// Initialize service
const service = require('feathers-sequelize')(app, {
Model: require('./models/user.model'),
paginate: options.paginate,
hooks: {
before: {
create: [allowlistHook],
update: [allowlistHook],
patch: [allowlistHook]
}
}
});
app.use('/users', service);
};
For PATCH requests, ensure that partial updates respect the same allowlist. Additionally, when dealing with multi-tenant Cockroachdb deployments, explicitly validate tenant_id against the authenticated context rather than trusting client input:
// src/hooks/tenant-hook.js
function tenantHook(context) {
if (context.params && context.params.user) {
const userTenantId = context.params.user.tenant_id;
if (context.data && context.data.tenant_id && context.data.tenant_id !== userTenantId) {
throw new Error('Forbidden: tenant_id mismatch');
}
// Explicitly set tenant from auth context
context.data.tenant_id = userTenantId;
}
return context;
}
module.exports = { tenantHook };
Combine these hooks to form a robust pipeline that prevents mass assignment while respecting Cockroachdb’s distributed consistency model. This approach aligns with OWASP API Top 10 (2023) A01:2023 and Property Authorization checks emphasized by middleBrick’s security framework.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |