Challenge 3: Fix an Open $fillable — Possible Solution ==================================================================== # BEFORE — vulnerable // app/Models/User.php class User extends Model { protected $guarded = []; // ⚠️ nothing is protected — every column is mass-assignable } // app/Http/Controllers/UserController.php public function store(Request $request) { $user = User::create($request->all()); // ⚠️ accepts EVERY submitted field return redirect()->route('users.index'); } # AFTER — safe // app/Models/User.php class User extends Model { protected $fillable = ['name', 'email']; // explicit allowlist } // app/Http/Controllers/UserController.php public function store(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|email', ]); $user = User::create($validated); return redirect()->route('users.index'); } WHAT A MALICIOUS REQUEST COULD HAVE DONE TO THE ORIGINAL VERSION -------------------------------------------------------------------- Because HTML form data (or a raw HTTP request built with curl/Postman) is just a flat set of key/value pairs, an attacker can submit EXTRA fields beyond whatever the legitimate registration form actually displays. With $guarded = [] and $request->all() passed directly into create(), User:: create() has no way to distinguish "fields the user is supposed to set" from "every column the users table happens to have." A request body like: name=Attacker&email=attacker@example.com&is_admin=1 would create a User row with is_admin actually set to true/1 in the database — granting administrative access the attacker was never supposed to have, entirely through a form field that was never rendered or intended to be submittable. This is the Eloquent-flavored version of the exact same vulnerability the Rails course's strong parameters chapter, Django's ModelForm fields = "__all__" gotcha, and DRF's serializer fields = "__all__" gotcha all warned about — four different frameworks, the same underlying mistake. WHY THIS WORKS -------------- - $fillable = ['name', 'email'] means User::create() (and update()) will ONLY ever write those two columns, regardless of what other keys exist in the array passed to it — is_admin simply isn't reachable through mass assignment on this model, no matter what a request contains. - $request->validate([...]) adds a second layer of defense: it restricts which keys are even extracted from the request in the first place (only name and email are validated and returned), and it rejects the request entirely with a 422 response if either field is missing or malformed — catching bad data before it ever reaches the database layer at all. - Using BOTH $fillable on the model AND request validation in the controller is standard practice, not redundant — validation ensures the submitted data is well-formed; $fillable ensures that even if validation were ever accidentally skipped or misconfigured somewhere, the model itself still can't be mass-assigned fields outside its allowlist.