HIGH mass assignmentadonisjsapi keys

Mass Assignment in Adonisjs with Api Keys

Mass Assignment in Adonisjs with Api Keys — how this specific combination creates or exposes the vulnerability

Mass assignment occurs when an API binds incoming request fields directly to a model or controller action without explicit allowlisting. In AdonisJS, the typical pattern uses merge from the @ioc:Adonis/Lucid/Orm helper to update a model instance with request body data. When API keys are used for authentication but authorization is missing or incomplete, mass assignment can unintentionally expose fields that should be immutable or privileged, such as role flags, permission scopes, or admin attributes. For example, an API key–based endpoint that accepts a JSON payload like {"username": "alice", "role": "admin"} and calls model.merge(payload) may apply the role change because the model does not block it.

This combination is risky when authentication (API keys) verifies identity but authorization is delegated solely to mass assignment guards. Attackers who obtain or guess a valid API key can probe endpoints for over-permissive models. If the model uses fillable or relies on a denylist, fields such as isActive, permissions, or subscriptionTier may be modified unintentionally. In AdonisJS, the framework does not automatically prevent mass assignment; it relies on developers to define fillable or guarded properties on the model. Without a strict allowlist, an API key–scoped session can lead to privilege escalation via BOLA or IDOR when combined with missing property-level checks.

Consider an endpoint that updates user profile information. The route is protected by an API key check that loads a user model, but the handler uses user.merge(request.body()). If fillable is not set to ["email", "username"] (or similar), a request adding "isAdmin": true can change the user’s privileges. This is a BOLA/IDOR risk scoped by the API key: the key identifies the user, but the mass assignment path determines what attributes can be altered. The scan category Property Authorization highlights this as a finding when updates are not restricted to intended fields.

Input validation and property authorization must align with the model’s definition. If validation only checks types or formats but does not filter fields, the API key authentication gives an attacker a stable identity to exploit over-permissive merging. For instance, a JSON schema that omits role may still allow it through if the body is not explicitly pruned before merging. Developers should treat API keys as identifiers, not as authorization boundaries, and enforce field-level permissions in the model or service layer to prevent mass assignment across authenticated but over-privileged paths.

Api Keys-Specific Remediation in Adonisjs — concrete code fixes

To remediate mass assignment with API keys in AdonisJS, explicitly define which fields are updatable and avoid merging raw request bodies. Use a strict allowlist in your models and validate/sanitize input before merging. Below are concrete, syntactically correct examples.

Example 1: Safe merge with fillable fields

Define fillable on your model to allow only specific fields. Then, in the controller, merge only the allowed subset or use a DTO to shape the update.

// app/Models/User.ts
import { DateTime } from 'luxon'
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'

export default class User extends BaseModel {
  @column({ isPrimary: true })
  public id: number

  @column()
  public email: string

  @column()
  public username: string

  @column()
  public isAdmin: boolean

  // Only these fields should be mass-assignable via API keys
  public static fillable = ['email', 'username']
}

// app/Controllers/Http/UserController.ts
import User from 'App/Models/User'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export default class UserController {
  public async updateProfile({ request, params, auth }: HttpContextContract) {
    const user = await User.findOrFail(params.id)
    // API key authentication would have loaded user via auth
    const payload = request.only(['email', 'username']) as Partial
    user.merge(payload)
    await user.save()
    return user
  }
}

Example 2: Using a DTO to enforce property authorization

Create a Data Transfer Object that whitelists updatable fields and validate before merging. This keeps mass assignment explicit and prevents accidental privilege changes.

// app/Validators/UserProfileValidator.ts
import { schema } from '@ioc:Adonis/Core/Validator'

export const userProfileSchema = schema.create({
  email: schema.string.optional([rules.email()]),
  username: schema.string.optional([rules.alpha()]),
  // Intentionally omitting isAdmin, role, permissions, etc.
})

export type UserProfileValidator = typeof userProfileSchema['shape']

// app/Controllers/Http/UserController.ts
import User from 'App/Models/User'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { userProfileSchema } from 'App/Validators/UserProfileValidator'

export default class UserController {
  public async updateProfile({ request, params }: HttpContextContract) {
    const payload = request.validate({ schema: userProfileSchema })
    const user = await User.findOrFail(params.id)
    // Merge only validated and allowed fields
    user.merge(payload)
    await user.save()
    return user
  }
}

Example 3: Explicit deny for sensitive fields

Use guarded as a safety net and combine with route-level checks to ensure API keys do not implicitly permit sensitive updates. This pattern is useful when your model has many fields and you prefer a denylist for critical attributes.

// app/Models/User.ts
import { DateTime } from 'luxon'
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'

export default class User extends BaseModel {
  @column({ isPrimary: true })
  public id: number

  @column()
  public email: string

  @column()
  public username: string

  @column()
  public isAdmin: boolean

  // Deny mass assignment for sensitive fields
  public static guarded = ['isAdmin', 'role', 'permissions', 'apiKey']
}

// Ensure routes that accept updates check ownership or scope
// app/Controllers/Http/UserController.ts
import User from 'App/Models/User'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export default class UserController {
  public async update({ request, params, auth }: HttpContextContract) {
    const user = await User.findOrFail(params.id)
    // API key may identify the user; ensure they can only update their own non-sensitive fields
    if (user.id !== auth.user?.id) {
      throw response.unauthorized('Unauthorized')
    }
    const allowed = request.only(['email', 'username'])
    user.merge(allowed)
    await user.save()
    return user
  }
}

These patterns ensure that API key authentication identifies the actor while property authorization and strict allowlists govern what can be changed. Combine these with runtime scans from middleBrick to detect mismatches between your fillable/guarded definitions and actual update behavior.

Related CWEs: propertyAuthorization

CWE IDNameSeverity
CWE-915Mass Assignment HIGH

Frequently Asked Questions

Does middleBrick fix mass assignment issues found in AdonisJS APIs?
middleBrick detects and reports mass assignment risks with remediation guidance; it does not automatically fix or patch your code.
How can I validate that my allowlist is sufficient for API key–scoped endpoints?
Use middleBrick’s property authorization checks and compare your model’s fillable/guarded lists against the fields your endpoints attempt to merge; adjust based on findings and test with representative payloads.