Every model must define $fillable (whitelist) or $guarded (blacklist).
Incorrect:
class User extends Model
{
protected $guarded = []; // All fields are mass assignable
}
Correct:
class User extends Model
{
protected $fillable = [
'name',
'email',
'password',
];
}
Never use $guarded = [] on models that accept user input.
Use policies or gates in controllers. Never skip authorization.
Incorrect:
public function update(UpdatePostRequest $request, Post $post)
{
$post->update($request->validated());
}
Correct:
public function update(UpdatePostRequest $request, Post $post)
{
Gate::authorize('update', $post);
$post->update($request->validated());
}
Or via Form Request:
public function authorize(): bool
{
return $this->user()->can('update', $this->route('post'));
}
Always use parameter binding. Never interpolate user input into queries.
Incorrect:
DB::select("SELECT * FROM users WHERE name = '{$request->name}'");
Correct:
User::where('name', $request->name)->get();
// Raw expressions with bindings
User::whereRaw('LOWER(name) = ?', [strtolower($request->name)])->get();
Use {{ }} for HTML escaping. Only use {!! !!} for trusted, pre-sanitized content.
Incorrect:
{!! $user->bio !!}
Correct:
{{ $user->bio }}
Include @csrf in all POST/PUT/DELETE Blade forms. In Inertia apps, the @csrf directive is automatically applied.
Incorrect:
<form method="POST" action="/posts">
<input type="text" name="title">
</form>
Correct:
<form method="POST" action="/posts">
@csrf
<input type="text" name="title">
</form>
Apply throttle middleware to authentication and API routes.
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by($request->ip());
});
Route::post('/login', LoginController::class)->middleware('throttle:login');
Validate extension, MIME type, and size. The mimes rule checks extensions; use mimetypes for actual MIME type validation. Never trust client-provided filenames.
public function rules(): array
{
return [
'avatar' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
];
}
Store with generated filenames:
$path = $request->file('avatar')->store('avatars', 'public');
Never commit .env. Access secrets via config() only.
Incorrect:
$key = env('API_KEY');
Correct:
// config/services.php
'api_key' => env('API_KEY'),
// In application code
$key = config('services.api_key');
Run composer audit periodically to check for known vulnerabilities in dependencies. Automate this in CI to catch issues before deployment.
composer audit
Use encrypted cast for API keys/tokens and mark the attribute as hidden.
Incorrect:
class Integration extends Model
{
protected function casts(): array
{
return [
'api_key' => 'string',
];
}
}
Correct:
class Integration extends Model
{
protected $hidden = ['api_key', 'api_secret'];
protected function casts(): array
{
return [
'api_key' => 'encrypted',
'api_secret' => 'encrypted',
];
}
}