CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-22814
8.20.03%

Lucid Dreams, Nightmare Reality: Mass Assignment in AdonisJS

Alon Barad
Alon Barad
Software Engineer

Feb 19, 2026·6 min read·10 visits

PoC Available

Executive Summary (TL;DR)

AdonisJS Lucid mixed internal state flags (like $isPersisted) with user data on the same object instance. Attackers can inject these flags via JSON payloads to trick the ORM into updating existing records instead of creating new ones, bypassing business logic and potentially taking over accounts.

A critical Mass Assignment vulnerability in the AdonisJS Lucid ORM allows attackers to overwrite internal model state flags. By injecting properties like '$isPersisted', attackers can hijack the Active Record lifecycle, turning harmless INSERT operations into malicious UPDATE queries.

The Hook: When Magic Becomes a Curse

ORMs are the software equivalent of a self-driving car. Most of the time, they get you to the grocery store safely while you nap. But occasionally, they confuse a concrete wall for an exit ramp. AdonisJS Lucid is a popular Active Record ORM that—like many of its predecessors—strives to make database interactions feel like native object manipulation.

Here’s the rub: Active Record patterns often mix data (your username, email, password) with state (is this record saved? is it deleted? what were the original values?). They all live on the same object instance. It's crowded in there.

CVE-2026-22814 is what happens when the bouncer at the club (the input validator) assumes that just because someone is wearing a wristband (is an 'own property'), they belong in VIP. In Lucid, this vulnerability allows a remote attacker to manipulate the internal organs of the ORM itself, simply by sending a JSON payload that the developer was too lazy to filter.

The Flaw: The `hasOwnProperty` Betrayal

To understand this bug, you have to understand how Lucid decides what to save to the database. When you call User.create(request.all()), Lucid takes that input object and merges it into a new Model instance. But it needs to know: "Is this key a database column, or is it garbage?"

In the vulnerable versions, the check was naively simple. The code essentially asked: "Does this model instance have a property with this name?" using this.hasOwnProperty(key).

Here is where the JavaScript prototype chain bites us. Lucid models have internal state properties initialized in the constructor. Properties like $isPersisted (which tracks if the row exists in the DB) or $attributes (the raw data) are own properties of the instance.

So, if an attacker sends "$isPersisted": true, the check model.hasOwnProperty('$isPersisted') returns true. The ORM nods enthusiastically and overwrites its own internal logic flag with the attacker's value. It's like convincing a bank teller you're the manager just because you're wearing a name tag you brought from home.

The Code: The Smoking Gun

Let's look at the crime scene. The vulnerability lived in src/orm/base_model/index.ts. The logic for merging values looked something like this:

// VULNERABLE CODE
merge(values: any) {
  Object.keys(values).forEach((key) => {
    // The fatal flaw: Internal properties satisfy this check
    if (this.hasOwnProperty(key)) {
      this[key] = values[key]
    }
  })
}

The fix, implemented in commit b007b12b40cc4a033bc06402b2e40d30fc9f3b85, introduces a hardcoded list of "naughty words"—internal properties that should never be touched by mass assignment.

// PATCHED CODE
const INTERNAL_INSTANCE_PROPERTIES = new Set([
  '$columns',
  '$attributes',
  '$isPersisted', // <--- The most dangerous one
  '$isDeleted',
  // ... others
])
 
merge(values: any) {
  Object.keys(values).forEach((key) => {
    // Explicitly block internal props
    if (INTERNAL_INSTANCE_PROPERTIES.has(key)) {
      return
    }
    if (this.hasOwnProperty(key)) {
      this[key] = values[key]
    }
  })
}

It’s a brute-force solution, but effective. If the key is on the list, it gets dropped, protecting the ORM's internal state from external tampering.

The Exploit: From Registration to Account Takeover

Let's weaponize this. Imagine a standard user registration endpoint. The developer, trusting the framework, writes this:

// POST /register
public async register({ request }: HttpContextContract) {
  // Dangerous: Passing all input directly to create
  const user = await User.create(request.all())
  return user
}

The Attack Chain:

  1. Target Selection: The attacker wants to overwrite the account of the admin, who usually has id: 1.
  2. Payload Construction: The attacker constructs a JSON body that claims the user is already persisted.
{
  "username": "hacked_admin",
  "email": "pwned@evil.com",
  "id": 1,
  "$isPersisted": true
}
  1. The Hijack:

    • Lucid creates a new User().
    • It merges the data. user.id becomes 1.
    • It sees $isPersisted and sets user.$isPersisted = true.
    • The save() method is called. It checks if (this.$isPersisted).
    • Because it's true, Lucid assumes this is an UPDATE, not an INSERT.
  2. The Execution: instead of failing on a Primary Key violation (inserting ID 1), the database executes: UPDATE users SET username = 'hacked_admin', email = 'pwned@evil.com' WHERE id = 1

Congratulations. You just overwrote the admin's credentials on a public registration form.

The Impact: Why We Should Panic

This isn't just about overwriting data. It's about fundamental logic bypass. The impact depends heavily on what internal flags are exposed.

  • Data Integrity Violation: As shown, forcing UPDATEs allows modifying arbitrary records if the ID is guessable.
  • Logic Bypass: Some applications use hooks like beforeCreate vs beforeUpdate to enforce security checks (e.g., hashing passwords only on create). By masquerading as an existing record, an attacker might bypass these lifecycle hooks entirely.
  • DoS: Setting $isDeleted: true on a new record might confuse the application logic, causing crashes or phantom records that exist in the DB but are ignored by the application queries.

In a CVSS v4.0 world, this scores an 8.2 (High) because it requires no privileges (PR:N) and attacks the integrity of the system (VI:H) through a very common coding pattern.

The Fix: Stop Being Lazy

The immediate fix is to upgrade @adonisjs/lucid to version 21.8.2 or 22.0.0-next.6. This applies the internal property denylist. However, relying on the ORM to sanitize your input is like relying on your car's airbag to save you while driving blindfolded.

Developer Takeaways:

  1. Never use request.all(): It is the root of all evil. If your model has a isAdmin column and you use request.all(), you are vulnerable to standard mass assignment even without this specific bug.
  2. Use Validation Libraries: Tools like @vinejs/vine or Zod act as a strict bouncer. They strip out anything not explicitly defined in the schema. If $isPersisted isn't in your validation schema, it never reaches the model.
// DO THIS
const data = await request.validateUsing(registerValidator)
User.create(data)

This vulnerability is a stark reminder: Input validation is not optional, and internal state should never be mixed with public data.

Official Patches

AdonisJSCommit fixing the mass assignment issue

Fix Analysis (1)

Technical Appendix

CVSS Score
8.2/ 10
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N
EPSS Probability
0.03%
Top 91% most exploited

Affected Systems

AdonisJS Applications using Lucid ORM

Affected Versions Detail

Product
Affected Versions
Fixed Version
@adonisjs/lucid
AdonisJS
< 21.8.221.8.2
@adonisjs/lucid
AdonisJS
>= 22.0.0-next.0, < 22.0.0-next.622.0.0-next.6
AttributeDetail
CWE IDCWE-915
Attack VectorNetwork
CVSS Score8.2 (High)
ImpactIntegrity Violation / Logic Bypass
Exploit StatusPoC Available
Affected ComponentLucid BaseModelImpl

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1565.002Data Manipulation: Transmitted Data Manipulation
Impact
CWE-915
Mass Assignment

Improperly Controlled Modification of Dynamically-Determined Object Attributes (Mass Assignment)

Known Exploits & Detection

GitHub AdvisoryAdvisory containing PoC logic for overriding $isPersisted

Vulnerability Timeline

Fix authored by Romain Lanz
2026-01-11
Version 21.8.2 Released
2026-01-12
GHSA-g5gc-h5hp-555f Published
2026-01-13

References & Sources

  • [1]GitHub Security Advisory GHSA-g5gc-h5hp-555f
  • [2]AdonisJS Lucid v21.8.2 Release Notes

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.