Mass Assignment in Adonisjs
How Mass Assignment Manifests in Adonisjs
Mass assignment vulnerabilities in Adonisjs occur when user-controlled data is directly passed to model creation or update methods without proper filtering. In Adonisjs, this typically happens when request body data flows directly into Model.create() or Model.update() calls.
Consider this vulnerable Adonisjs controller pattern:
class UserController {
async store({ request }) {
const data = request.body();
const user = await User.create(data); // <-- Vulnerable
return user;
}
async update({ request, params }) {
const data = request.body();
const user = await User.findOrFail(params.id);
await user.merge(data); // <-- Vulnerable
await user.save();
return user;
}
}The vulnerability arises because Adonisjs's Lucid ORM doesn't automatically filter incoming data. When an attacker sends additional fields in their request, those fields get persisted to the database if they exist on the model.
Common attack patterns in Adonisjs applications:
- Role Escalation: Adding isAdmin=true to user creation requests
- Property Tampering: Modifying internal fields like balance, credits, or status
- Data Spoofing: Changing createdAt, updatedAt timestamps to manipulate audit trails
- Foreign Key Manipulation: Setting arbitrary owner_id or user_id values to access other users' data
Adonisjs's Lucid models expose all database columns by default, making every column a potential attack vector. Without explicit protection, any field the attacker discovers can be manipulated.
Adonisjs-Specific Detection
Detecting mass assignment vulnerabilities in Adonisjs requires examining both code patterns and runtime behavior. Here are Adonisjs-specific detection methods:
Code Pattern Analysis
Look for these anti-patterns in your Adonisjs controllers:
// Vulnerable patterns
const data = request.body();
await Model.create(data);
await model.merge(data);
await model.fill(data); // <-- fill() is particularly dangerousmiddleBrick's Adonisjs-specific scanning identifies these patterns automatically. The scanner analyzes your API endpoints and flags instances where request data flows directly into model operations without filtering.
Schema Analysis
middleBrick examines your Adonisjs Lucid models to identify:
- Fillable fields that could be mass-assigned
- Hidden fields that might be exposed through mass assignment
- Database columns that lack proper authorization controls
Runtime Testing
middleBrick actively tests for mass assignment by:
- Scanning your OpenAPI spec to understand model schemas
- Submitting requests with additional fields not present in the documented schema
- Verifying if those fields are accepted and persisted
- Checking if protected fields like role, admin, or internal IDs can be manipulated
Integration with Adonisjs Features
middleBrick understands Adonisjs-specific constructs like:
- Lazy-loaded relationships that might be vulnerable to BOLA attacks
- Middleware chains that could bypass authorization
- Validator schemas that might not cover all model fields
Adonisjs-Specific Remediation
Adonisjs provides several native mechanisms to prevent mass assignment vulnerabilities. Here's how to implement them correctly:
1. Using $fillable Property
The most straightforward approach is defining a $fillable array on your Lucid models:
class User extends BaseModel {
static $fillable = ['username', 'email', 'password'];
// All other fields are automatically protected
}
// In your controller
class UserController {
async store({ request }) {
const data = request.only(['username', 'email', 'password']);
const user = await User.create(data); // Only $fillable fields are accepted
return user;
}
}2. Using $guard Property
Alternatively, use $guard to explicitly block specific fields:
class User extends BaseModel {
static $guard = ['isAdmin', 'role', 'balance', 'owner_id'];
}
// Now any attempt to set these fields will be ignored
3. Explicit Field Selection with request.only()
Always use request.only() instead of request.body():
class ProductController {
async update({ request, params }) {
const data = request.only(['name', 'description', 'price']);
const product = await Product.findOrFail(params.id);
// Additional authorization check
if (product.user_id !== request.user.id) {
throw new Error('Unauthorized');
}
await product.merge(data);
await product.save();
return product;
}
}
4. Custom Save Methods with Validation
For complex scenarios, create dedicated save methods:
class UserService {
async createUser(data, authUser) {
// Remove any unauthorized fields
delete data.isAdmin;
delete data.role;
// Additional business logic
data.createdBy = authUser.id;
return await User.create(data);
}
}
5. Middleware for Global Protection
Create a middleware to enforce mass assignment protection:
class MassAssignmentMiddleware {
async handle({ request, response }, next) {
const model = request.input('model');
if (model) {
const allowed = model.$fillable || [];
const data = request.only(allowed);
request.body(data); // Replace body with filtered data
}
await next();
}
}
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |