Files
Andreas Reinhold / reiniandClaude Opus 5 92b3b3de56 Regenerate Boost guidelines and skills
Generated by boost:update for Boost 2.8, which replaces the pest-testing
skill with testing-best-practices and adds infer-conventions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYnWFt9pJEwvAmNFN38XD
2026-09-10 10:42:22 +02:00

5.7 KiB

Security Best Practices

Control Mass Assignment

Define $fillable when a model is populated from request-derived arrays, or deliberately guard attributes by another consistent model convention. Laravel models guard all attributes by default; $guarded = [] opts out of that protection.

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

Do not pass untrusted request data to a model with $guarded = []. Mass-assignment protection controls which attributes create(), fill(), and update() may set; it does not validate values or authorize the operation.

Authorize Protected Actions

Use policies, gates, or form request authorization for actions that depend on the current user's permissions. Authentication alone does not establish permission, and validation is not authorization.

public function update(UpdatePostRequest $request, Post $post): RedirectResponse
{
    Gate::authorize('update', $post);

    $post->update($request->validated());

    return redirect()->route('posts.show', $post);
}

Authorization may instead live in the form request:

public function authorize(): bool
{
    return $this->user()?->can('update', $this->route('post')) ?? false;
}

Public actions intentionally available to everyone do not need a redundant authorization check.

Bind Query Parameters

Use Eloquent, the query builder, or explicit bindings instead of interpolating untrusted values into Structured Query Language (SQL). Bindings protect values, not identifiers such as column names or sort directions; map user-selected identifiers to an allow-list.

Incorrect:

DB::select("SELECT * FROM users WHERE name = '{$request->name}'");

Correct:

User::where('name', $request->name)->get();
User::whereRaw('LOWER(name) = ?', [$request->string('name')->lower()->toString()])->get();

Escape Output in Its Context

Blade's {{ }} syntax HTML-escapes output. Use {!! !!} only for content that has been sanitized for the exact HTML context in which it is rendered. Escaping rules differ for HTML, URLs, JavaScript, and Cascading Style Sheets.

Incorrect for untrusted content:

{!! $user->bio !!}

Correct:

{{ $user->bio }}

Apply Cross-Site Request Forgery Protection

Include @csrf in state-changing Blade forms handled by Laravel's web middleware. Routes intentionally excluded from cross-site request forgery (CSRF) verification, such as validated third-party webhooks, need their own authenticity check.

<form method="POST" action="/posts">
    @csrf
    <input type="text" name="title">
</form>

Inertia applications commonly use Axios, which returns the encrypted XSRF-TOKEN cookie in the X-XSRF-TOKEN header. Confirm equivalent configuration when using another HTTP client. Do not disable CSRF protection merely to fix a token mismatch.

Rate Limit Sensitive Endpoints

Apply suitable rate limits to login attempts, password recovery, verification messages, and expensive or abuse-prone application programming interface (API) routes. Choose the limiter key deliberately; an Internet Protocol (IP) address alone can unfairly group users behind a shared network, while an account identifier alone can enable targeted denial of service.

RateLimiter::for('login', function (Request $request) {
    return Limit::perMinute(5)->by(Str::transliterate(
        Str::lower($request->string('email')).'|'.$request->ip()
    ));
});

Route::post('/login', LoginController::class)->middleware('throttle:login');

Rate limiting reduces abuse; it does not replace authentication, authorization, or upstream denial-of-service protection.

Validate and Store Uploads Safely

Validate expected content type, dimensions where relevant, and size. Laravel's mimes rule reads the file contents and guesses a Multipurpose Internet Mail Extensions (MIME) type corresponding to the listed extensions; it does not validate the user-assigned filename extension. The extensions rule checks that extension and should not be used by itself.

public function rules(): array
{
    return [
        'avatar' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
    ];
}

Use Laravel's storage methods to generate a filename, and store untrusted files outside a publicly executable location. Public files can require additional controls, such as image re-encoding, content-disposition headers, and explicit blocking of active formats.

$path = $request->file('avatar')->store('avatars');

Keep Secrets Out of Application Code

Do not commit populated environment files or hard-code credentials. Read environment variables in configuration files, then use config() in application code so configuration caching works correctly. See the configuration rules for encrypted environment files and external secret stores.

Audit Dependencies

Run composer audit regularly and in continuous integration. Review findings for exploitability and update or mitigate affected packages promptly.

composer audit

Encrypt Sensitive Attributes When Appropriate

Use an encrypted cast for sensitive values that must be recoverable, and use $hidden to omit them from array and JavaScript Object Notation (JSON) serialization. Hidden attributes remain accessible in PHP, and encryption does not replace access control. Encrypted values cannot be meaningfully queried and should use a TEXT or larger column because ciphertext length is variable.

class Integration extends Model
{
    protected $hidden = ['api_key', 'api_secret'];

    protected function casts(): array
    {
        return [
            'api_key' => 'encrypted',
            'api_secret' => 'encrypted',
        ];
    }
}