Back to Blog
Lesson 31 of the Advanced Laravel: Architecture, Scaling & Performance course
LaravelJune 28, 20264 min read

Mass Assignment Hardening: Securing Eloquent Models

Mass assignment is a critical security vulnerability where attackers inject unauthorized fields into your database. Learn to harden your Laravel models today.

LaravelSecurityEloquentArchitectureMass Assignmentphpbackend

Previously in this course, we explored Defense Against SSRF: Hardening Laravel Outgoing Requests to protect our perimeter from server-side request forgery. While SSRF secures our outbound traffic, we must now turn our attention inward to the data integrity of our Eloquent models.

Mass assignment occurs when an application accepts an array of input directly from an HTTP request and passes it into a model's create() or update() method without sufficient filtering. If your User model is accidentally configured to allow mass assignment on an is_admin column, an attacker can simply pass is_admin=1 in their JSON payload to escalate their privileges.

The First Principle: Explicit Intent

In a production-grade SaaS, "implicit" is dangerous. Relying on default behavior is the primary driver of mass assignment vulnerabilities. You must transition from a "permit all" mindset to a "deny by default" architecture.

Eloquent provides two primary gates for this: $fillable (the whitelist) and $guarded (the blacklist). In high-traffic systems, I strongly advise against using $guarded = []. Even if you think you are "safe" because you use DTOs, a single developer mistake in a controller can expose your entire schema.

Enforcing Strict Policies

Always define an explicit $fillable array. If a field isn't in that list, it should not be touched by Eloquent's mass assignment methods.

PHP
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    #6A9955">// The ONLY fields allowed to be set via mass assignment
    protected $fillable = [
        'name',
        'email',
        'password',
    ];
}

If you find yourself frequently using unguarded() or passing massive arrays into models, you are likely bypassing your domain boundaries. As discussed in Utilizing Data Transfer Objects (DTOs), your models should represent persistent state, not raw HTTP input.

Hardening with FormRequests

While $fillable provides a safety net at the database layer, it is a reactive measure. We want to be proactive. FormRequests are the first line of defense; they sanitize and validate input before it ever touches your service layer or your models.

By defining a FormRequest, you create a contract for your incoming data. If the input doesn't match the schema, the request is terminated.

Worked Example: Secure User Update

Instead of passing $request->all() into an update() call, we define a specific request class to filter out malicious fields.

PHP
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UpdateUserRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users,email,' . $this->user()->id,
            #6A9955">// 'is_admin' is intentionally omitted!
        ];
    }
}

Now, in your Controller or Action class:

PHP
public function update(UpdateUserRequest $request, User $user)
{
    #6A9955">// $request->validated() only contains the fields defined in rules()
    $user->update($request->validated());
}

Even if an attacker sends {"is_admin": 1, "name": "Hacked"}, the is_admin key is stripped away by validated(), and the model's $fillable array acts as a final fail-safe.

Hands-on Exercise

  1. Identify a model in your current project that currently uses $guarded = [].
  2. Replace it with an explicit $fillable array containing only the fields intended for user input.
  3. Create a FormRequest for a common update endpoint.
  4. Verify that attempting to inject an extra field (like a role or status column) fails to update the database even when sent in the request.

Common Pitfalls

  • The $guarded = [] Trap: Never use this in production. It is a "lazy" developer pattern that creates massive security debt.
  • Trusting request()->all(): Never pass the entire request object to a model. Always use validated() or map data into a DTO.
  • Partial Updates: When performing partial updates, developers often forget that update() will attempt to save every key in the array. If you are passing an object from a frontend state, ensure you are filtering keys before calling the model.

Summary

Mass assignment hardening is about reducing the surface area of your models. By combining strictly defined $fillable arrays with the rigorous validation provided by FormRequests, you create a multi-layered defense that prevents unauthorized data manipulation. For more on how this integrates with broader architecture, review Preventing Mass Assignment: A Guide to Secure DTO Implementation.

Up next: We will dive into Automated Security Testing, where we'll implement static analysis tools to catch these vulnerabilities before they ever hit your CI/CD pipeline.

Similar Posts