Open-Source Wikis

/

Laravel

/

Security

laravel/laravel

Security

The skeleton ships several security-minded defaults. Most of them are inherited from laravel/framework, but a few decisions live here in this repo's config files. This page catalogues what's already protecting you and what you have to do yourself.

Reporting vulnerabilities

The repo's README.md Security Vulnerabilities section is unambiguous:

If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via taylor@laravel.com. All security vulnerabilities will be promptly addressed.

Don't open public issues or PRs for vulnerabilities — email first.

Defaults baked into the skeleton

Encryption key separation

config/app.php splits the encryption key from the rest of the config:

'cipher' => 'AES-256-CBC',
'key' => env('APP_KEY'),
'previous_keys' => [
    ...array_filter(
        explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
    ),
],

previous_keys (an array_filter(explode(','...))) lets you rotate APP_KEY while still decrypting payloads encrypted with previous keys. Without this, key rotation breaks existing sessions, queued jobs, and signed URLs.

Password hashing cast

app/Models/User.php declares:

protected function casts(): array
{
    return [
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
    ];
}

The hashed cast (introduced in Laravel 10) ensures any value assigned to $user->password is automatically passed through Hash::make(). You can't accidentally store a plaintext password — even User::factory()->create(['password' => 'secret']) produces a bcrypt hash.

BCRYPT_ROUNDS=12 in .env.example is the default cost factor. The CI test config (phpunit.xml) lowers it to 4 so tests run fast.

Session serialization defaults to JSON, not PHP

config/session.php ends with:

'serialization' => 'json',

Setting this to 'php' allows storing PHP objects in the session — which sounds convenient but opens a serialization-gadget attack surface if APP_KEY ever leaks. The default is JSON, and the file's comment block explicitly calls out the gadget-chain risk.

Cache deserialization defaults to no classes

config/cache.php:

'serializable_classes' => false,

Same story — a 2026-02-18 hardening (commit 8a924774) that prevents the database cache store from rebuilding arbitrary objects out of cached payloads. Setting this to a list of class names is opt-in.

CSRF protection (framework default)

The web middleware group automatically applies VerifyCsrfToken. Every Blade form template should include @csrf, which emits a hidden _token input.

The skeleton ships no App\Http\Middleware\VerifyCsrfToken override — the framework's default applies, which validates POST/PUT/PATCH/DELETE requests against the session token.

To disable CSRF for specific routes (e.g. webhook endpoints), use the validateCsrfTokens configuration inside bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->validateCsrfTokens(except: [
        'webhooks/*',
    ]);
})

config/session.php:

'same_site' => env('SESSION_SAME_SITE', 'lax'),

lax mitigates CSRF on top-level navigations while still allowing the session cookie to flow on same-site form submits and standard navigations. strict is the most paranoid; none is required for embedded cross-site contexts (and must be paired with secure).

Partitioned cookies (CHIPS)

'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),

Off by default. Set SESSION_PARTITIONED_COOKIE=true if your app embeds inside another site and needs Cookies-Having-Independent-Partitioned-State semantics.

HTTPS-only cookies

'secure' => env('SESSION_SECURE_COOKIE'),

The default is null — i.e. the framework uses the request scheme to decide. In production you should explicitly set SESSION_SECURE_COOKIE=true so browsers refuse to send the session cookie over HTTP.

'http_only' => env('SESSION_HTTP_ONLY', true),

On by default. JavaScript can't read the session cookie. Don't disable this.

Supply-chain hardening (2026)

Three commits in March-April 2026 tightened the JS toolchain after a supply-chain incident:

  • 2026-03-31 02ec8155 [security] pin axios version (#6776) — pinned axios to a specific patch version after a malicious release was published.
  • 2026-04-01 371cc0b8 Remove axios and enable ignore-scripts (#6778) — removed axios from the skeleton entirely. package.json now ships zero runtime npm dependencies.
  • 2026-04-15 9d195133 enable npm audit by default (#6788) — .npmrc adds audit=true, so npm install warns loudly about vulnerable transitive deps.

.npmrc ends up at:

ignore-scripts=true
audit=true

ignore-scripts=true prevents lifecycle scripts (postinstall, preinstall, etc.) from any installed package from executing on your machine. This is the single biggest npm-supply-chain hardening you can apply.

Things you must do yourself

Set a real APP_KEY

.env.example ships with APP_KEY= (empty). The composer setup script and post-create-project-cmd hook run php artisan key:generate to fill it in. Verify before deploying — the framework will throw at runtime if the key is missing.

Use HTTPS

The skeleton doesn't force HTTPS. To redirect HTTP → HTTPS in production, set APP_URL=https://... in .env and ensure your web server / load balancer terminates TLS and forwards X-Forwarded-Proto. The framework's TrustProxies middleware (auto-applied by the web group in Laravel 11+) reads that header and treats the request as secure.

For belt-and-braces, force HTTPS in the app via URL::forceScheme('https') inside App\Providers\AppServiceProvider::boot() when app()->environment('production').

Validate input

Every controller method that handles user input should validate. The framework provides $request->validate(...) and form requests (php artisan make:request StoreUserRequest). The skeleton doesn't ship any controllers, so there's no example here — but it's the most consequential security responsibility you'll inherit.

Configure dontFlash for sensitive fields

Inside bootstrap/app.php's withExceptions():

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontFlash([
        'current_password',
        'password',
        'password_confirmation',
        'token',
        'secret',
    ]);
})

Without this, validation-failure redirects can flash these fields back into the session and expose them in URLs / debug output. The framework handles the standard password/password_confirmation pair by default but you should add anything else sensitive your app uses.

Lock down debug surfaces in production

Surface Production action
APP_DEBUG Set to false
php artisan tinker Don't expose; it's a dev tool
Telescope / Pulse If installed, gate behind authentication and IP allow-listing in production
APP_URL Set correctly so generated URLs and signed routes are stable

phpunit.xml proactively sets PULSE_ENABLED=false, TELESCOPE_ENABLED=false, and NIGHTWATCH_ENABLED=false so installing those packages doesn't break tests — but they're still on by default in production once installed. Read the relevant package docs before turning them on.

Rate-limit your routes

Laravel's ThrottleRequests middleware (alias throttle) is the standard control. The default api middleware group applies throttle:api (60/minute per user). The skeleton has no routes/api.php so this is moot until you add one — but throttle is also valid on web routes:

Route::post('/login', [LoginController::class, 'store'])
    ->middleware('throttle:5,1');   // 5 attempts per minute

Store secrets out of source control

.gitignore already excludes .env, .env.backup, .env.production, and auth.json. Never override that. Use your deployment platform's secret store.

Trust boundaries

The skeleton's only trust boundary out of the box is authenticated user vs. unauthenticated request, mediated by:

  • App\Models\User (the principal)
  • config/auth.php web guard (the session-based authenticator)
  • password_reset_tokens table (the password reset issuance/validation surface)
  • sessions table (where authenticated session state persists when SESSION_DRIVER=database)

There is no role/permission system, no policy classes, no audit log. As soon as you add multi-tenant logic, role-based authorization, or sensitive data, you're stepping outside what this skeleton ships and need to build the controls yourself.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Security – Laravel wiki | Factory