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.
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.
PHPnamespace 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.
PHPnamespace 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:
PHPpublic 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
- Identify a model in your current project that currently uses
$guarded = []. - Replace it with an explicit
$fillablearray containing only the fields intended for user input. - Create a
FormRequestfor a common update endpoint. - Verify that attempting to inject an extra field (like a
roleorstatuscolumn) 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 usevalidated()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.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs โ the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

FilamentPHP Admin Panel & Dashboard Development
A powerful admin panel for your Laravel app โ built with FilamentPHP so you can manage everything without touching the database.