HIGH mass assignmentadonisjs

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 dangerous

middleBrick'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:

  1. Scanning your OpenAPI spec to understand model schemas
  2. Submitting requests with additional fields not present in the documented schema
  3. Verifying if those fields are accepted and persisted
  4. 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 IDNameSeverity
CWE-915Mass Assignment HIGH

Frequently Asked Questions

How does middleBrick detect mass assignment vulnerabilities in Adonisjs applications?
middleBrick scans your Adonisjs API endpoints by analyzing the OpenAPI spec to understand model schemas, then actively tests for mass assignment by submitting requests with additional fields not present in the documented schema. It checks if those fields are accepted and persisted, and verifies if protected fields like role, admin, or internal IDs can be manipulated. The scanner also examines your Lucid models for $fillable and $guard properties to assess the current protection level.
What's the difference between $fillable and $guard in Adonisjs?
In Adonisjs Lucid models, $fillable is a whitelist approach where you explicitly list fields that CAN be mass-assigned, and all other fields are automatically protected. $guard is a blacklist approach where you explicitly list fields that CANNOT be mass-assigned, and all other fields are allowed. The $fillable approach is generally considered more secure because it follows the principle of least privilege—you only allow what you explicitly need.