Laravel Mass Assignment Vulnerability: How to Protect Eloquent Models with $fillable and $guarded

Eloquent models without $fillable or $guarded allow attackers to set any database column through request input, including is_admin, role, or email_verified_at.

High severity Application Security Updated 2026-05-01 Markdown

Mass assignment is a software development convenience feature — the ability to populate a model's attributes from an array in a single operation. Laravel's Eloquent ORM supports this through methods like create(), update(), and fill(). The vulnerability arises because HTTP request bodies are also arrays, meaning anything a user submits can end up in that array unless the developer explicitly restricts it using $fillable or $guarded.

When an attacker targets a mass assignment vulnerability, they add extra fields to a legitimate form submission. A user profile update form that accepts name and email might silently accept is_admin=1 or role=superadmin if the model has no allowlist. The attacker inspects the form, adds extra parameters with a browser extension or cURL, and submits. There is no exploit code required — just an unexpected field whose name matches a database column.

Mass assignment vulnerabilities are consistently in the OWASP Top 10. The most widely cited real-world example is the 2012 GitHub incident where a researcher exploited mass assignment in Ruby on Rails to add an SSH key to any repository, including GitHub's own. Laravel's $fillable and $guarded were designed to prevent this class of vulnerability, but they require explicit configuration on every model — no protection is applied automatically.

The Problem

Mass assignment occurs when an Eloquent model accepts all request input without restriction. If a model lacks $fillable or $guarded, an attacker can add extra fields to a form submission — like is_admin=1 or role=superadmin — and those values get written directly to the database. This is one of the most common Laravel vulnerabilities and the reason the framework includes mass assignment protection by default.

How to Fix

  1. 1

    Add $fillable to every Eloquent model

    Explicitly list the columns that can be mass-assigned:

    class User extends Model
    {
        protected $fillable = [
            'name',
            'email',
            'password',
        ];
    }

    Never include sensitive columns like is_admin, role, email_verified_at, or balance in $fillable.

  2. 2

    Or use $guarded as a blocklist

    If you prefer a blocklist approach, set $guarded to protect specific columns:

    class User extends Model
    {
        protected $guarded = [
            'id',
            'is_admin',
            'role',
            'email_verified_at',
        ];
    }
    Warning: $guarded = [] (empty array) disables all protection and is equivalent to having no mass assignment guard at all. Never use this in production.
  3. 3

    Validate and filter input before creating models

    Even with $fillable, always validate input first:

    $validated = $request->validate([
        'name' => 'required|string|max:255',
        'email' => 'required|email|unique:users',
    ]);

    User::create($validated);

    Using $request->validate() returns only the validated fields, providing a second layer of protection beyond $fillable.
  4. 4

    Audit existing models for missing protection

    Search your codebase for models without mass assignment protection:
    grep -rL 'fillable\|guarded' app/Models/

    This lists all model files that don't contain either $fillable or $guarded. Fix each one before deploying.

How to Verify

Test by sending an unexpected field in a request:

curl -X POST https://yourapp.com/api/users \
  -d 'name=Test&email=test@test.com&is_admin=1'

Then check the database — the is_admin column should not be set to 1. Also run the Stackshield scanner to verify: php artisan stackshield:scan --check=SS001

Prevention

Use a Pint or PHPStan rule to flag models without $fillable. Add mass assignment checks to your CI pipeline. Prefer $fillable (allowlist) over $guarded (blocklist) for stronger defaults. Use StackShield to continuously monitor for unprotected models.

Frequently Asked Questions

Is $guarded = [] ever acceptable?

Almost never. Some developers use it during rapid prototyping, but it should never reach production. If you need maximum flexibility, use $guarded with an explicit list of protected columns rather than an empty array.

Does Form Request validation prevent mass assignment?

Partially. Form Requests validate input types and values, but the validated data still goes through Eloquent mass assignment. Both layers are needed: validation ensures correct data types, $fillable ensures only allowed columns are written.

What about Model::unguarded() in seeders?

Model::unguarded() temporarily disables mass assignment protection and is common in database seeders. This is safe in seeders since they run in controlled environments, but never use it in controllers, jobs, or any code that handles user input.

Can mass assignment happen with Eloquent's update() method?

Yes. $model->update($request->all()) is just as vulnerable as User::create($request->all()). Both pass arbitrary arrays to Eloquent. Always use $request->validated() and define $fillable on the model to prevent unexpected columns from being written on both create and update operations.

How do I protect nested relationship data from mass assignment?

Define $fillable independently on each related model. If you use User::create() with nested profile data, the Profile model also needs its own $fillable. Never assume parent model protection extends to related models — each model must explicitly declare which columns it allows to be mass-assigned.

What is MassAssignmentException and when does it fire?

Laravel throws MassAssignmentException when you attempt to mass-assign a field that is not in $fillable and $guarded is not set to []. This exception is a safety signal, not an error to suppress with try/catch. If you see it in production logs, define $fillable on the model rather than disabling the guard.

Related Security Terms

Free security check

Is your Laravel app exposed right now?

34% of Laravel apps we scan have at least one critical issue. Most teams don't find out until something breaks. Our free scan checks your live application in under 60 seconds.

18% have debug mode on
72% missing security headers
12% have exposed .env
Scan My App Free No signup required. Results in 60 seconds.