AI QA Monkey
AI Security Intelligence
Laravel

Laravel Production Security Checklist: .env, Debug, Auth & Queue Safety

Laravel defaults are developer-friendly, but production environments require strict hardening. This checklist focuses on the controls that close common breach paths first.

Critical baseline

  • Set APP_DEBUG=false in production.
  • Block public access to .env and backups.
  • Rotate APP_KEY and sensitive service credentials as needed.

Auth and session controls

  • Enforce strong password + MFA where applicable.
  • Use secure session/cookie flags.
  • Throttle login/reset endpoints.

Queue, jobs, and deployment

  • Run workers with least privileges.
  • Sanitize job payloads and reduce sensitive logging.
  • Validate deploy pipeline secrets and artifact exposure.

Copy-paste Laravel production hardening checklist

# .env production baseline
APP_ENV=production
APP_DEBUG=false
LOG_LEVEL=error
SESSION_SECURE_COOKIE=true
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=strict

# Block .env access in Apache .htaccess
<FilesMatch "^\.env">
  Order allow,deny
  Deny from all
</FilesMatch>

# Nginx equivalent
location ~ /\.env {
  deny all;
  return 403;
}

Database and secrets hygiene

  • Each application environment (staging, production) must use a separate database user with minimum required permissions.
  • Never use root database credentials in .env. Create a dedicated user: GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO laravel_user@localhost;
  • Store secrets in environment variables or a vault — never commit them to Git. Review the exposed .env file fix guide.
  • Rotate APP_KEY using php artisan key:generate after any suspected credential leak.

Security headers for Laravel

  • Add security headers middleware to app/Http/Kernel.php using a package like spatie/laravel-csp for CSP.
  • Apply Strict-Transport-Security, X-Frame-Options: SAMEORIGIN, and X-Content-Type-Options: nosniff globally.
  • Review the complete security headers guide for copy-paste Apache/Nginx configs.
  • Use AI QA Monkey free security scanner to verify all headers are live in production.

CORS configuration for Laravel APIs

  • Configure config/cors.php with explicit allowed origins — never use * for authenticated APIs.
  • Review the CORS misconfiguration fix guide for Laravel-specific patterns.

Input validation and mass assignment protection

Laravel Eloquent's mass assignment feature is one of the most frequently exploited Laravel vulnerabilities. Attackers inject unexpected fields through POST requests to elevate privileges or corrupt data.

  • Always define $fillable or $guarded: Never leave both empty. protected $guarded = [] makes all columns mass-assignable — the most dangerous default.
  • Use Form Request validation: Replace inline $request->validate() calls with dedicated FormRequest classes that enforce type, format, and business rules per endpoint.
  • Validate file uploads strictly: Use mimes, max, and dimensions rules. Store uploads outside the public/ directory and serve through a signed URL controller.
  • Escape output in Blade templates: Always use {{ }} (auto-escaped) instead of {!! !!} (raw). Audit every {!! !!} use case for XSS exposure.

Route and middleware security

Laravel routes define the entire attack surface of your API. Every unprotected route is a potential entry point.

  • Apply auth middleware globally: Use route groups with middleware('auth') and explicitly whitelist public routes. Avoid relying on developers to remember to add auth per route.
  • Rate limiting on sensitive routes: Apply throttle middleware to login, password reset, registration, and any resource-creation endpoint. Default: 5 attempts per minute for auth routes.
  • Audit api.php vs web.php: API routes lack CSRF protection by default. Ensure all state-changing API endpoints use token authentication (Sanctum, Passport) rather than session-based auth.
  • Remove unused routes: Run php artisan route:list and audit every route. DELETE unused routes — they are undocumented attack surface.

Logging and monitoring for production

Silent failures are dangerous in production Laravel apps. Proper logging captures the evidence needed to detect intrusions and debug security incidents.

  • Set LOG_LEVEL=error in .env production to avoid logging sensitive request data.
  • Integrate with a log aggregation platform (Papertrail, Logtail, CloudWatch) to alert on error spikes, 500 responses, and authentication failures.
  • Never log raw request bodies or query strings containing tokens or passwords. Use $request->except(['password', 'token']) in log calls.
  • Monitor failed_jobs table for queue failures — failed jobs can indicate serialization attacks or dependency issues in deserialized payloads.

Dependency and package security

PHP package vulnerabilities are a growing attack vector. The Composer ecosystem has seen multiple supply chain incidents.

  • Run composer audit (available since Composer 2.4) on every deployment to detect packages with known CVEs.
  • Pin package versions in composer.lock and commit it to version control. Never run composer update in production without testing.
  • Remove development dependencies from production: composer install --no-dev --optimize-autoloader.
  • Subscribe to PHP security advisories via FriendsOfPHP/security-advisories or integrate Snyk/Dependabot into your CI pipeline.

Deployment pipeline hardening

The deployment process is a common source of secret leaks. CI/CD pipelines that log environment variables or store artifacts insecurely can expose production credentials.

  • Store secrets exclusively in CI/CD secret stores (GitHub Actions Secrets, GitLab CI Variables) — never in .env files committed to the repository.
  • Use deployment user accounts with minimal SSH permissions. Avoid deploying as root.
  • Run php artisan config:cache, route:cache, and view:cache post-deploy to optimize and validate configuration.
  • Set up a post-deploy health check that verifies APP_DEBUG=false and tests a protected endpoint to catch misconfiguration before traffic hits.

Laravel Sanctum and Passport: API authentication hardening

Laravel Sanctum (SPA and mobile auth) and Passport (full OAuth2) have distinct security models. Teams frequently misconfigure both, leading to token exposure and privilege escalation.

  • Sanctum token expiration: By default, Sanctum personal access tokens do not expire. Set a global expiration in config/sanctum.php: 'expiration' => 60 * 24 (24 hours in minutes). For high-security applications, use shorter windows and implement token refresh.
  • Sanctum SPA CSRF: SPA authentication via Sanctum requires the frontend to first call /sanctum/csrf-cookie to get a CSRF cookie. If this step is skipped or the SESSION_DOMAIN is misconfigured, CSRF protection is bypassed.
  • Passport scope restriction: Define explicit scopes for each OAuth client and validate scopes on protected routes using ->middleware('scope:read-users'). Tokens without scope restrictions grant access to all endpoints.
  • Revoke tokens on password change: When a user changes their password, all existing tokens should be revoked. Implement this in the password update controller: $user->tokens()->delete() for Sanctum or $user->oauthAccessToken()->delete() for Passport.
  • Sanctum stateful domains: Verify SANCTUM_STATEFUL_DOMAINS in .env lists only production frontend domains. An overly broad wildcard allows CSRF attacks from unexpected origins.

Laravel SQL injection: parameterization and raw queries

Laravel's Eloquent ORM parameterizes queries automatically. The vulnerability appears when developers bypass the ORM for performance and use raw queries incorrectly.

// UNSAFE — string interpolation in raw query
$users = DB::select("SELECT * FROM users WHERE name = '$name'");

// SAFE — parameterized bindings
$users = DB::select("SELECT * FROM users WHERE name = ?", [$name]);

// SAFE — named bindings
$users = DB::select("SELECT * FROM users WHERE name = :name", ['name' => $name]);

// SAFE — Eloquent (auto-parameterized)
$users = User::where('name', $name)->get();

// UNSAFE — orderBy with user input (column names cannot be parameterized)
$users = User::orderBy($request->sort)->get();  // SQL injection if $sort is not validated

// SAFE — validate against allowlist for dynamic column names
$allowedColumns = ['name', 'email', 'created_at'];
$sort = in_array($request->sort, $allowedColumns) ? $request->sort : 'created_at';
$users = User::orderBy($sort)->get();

File upload security in Laravel

File uploads are a high-risk feature. Improperly handled uploads allow attackers to upload PHP web shells, malware, or oversized files that exhaust disk space.

  • Validate MIME type server-side: Use mimes:jpg,jpeg,png,pdf in Form Request rules. Do not rely on the file extension — validate the actual MIME type using finfo or Laravel's built-in MIME validation.
  • Store outside public directory: Use storage/app/private/ for uploaded files and serve them through a controller with authorization checks: Storage::disk('private')->path($filename).
  • Rename uploaded files: Never use the original filename. Generate a UUID or hash: $file->storeAs('uploads', Str::uuid() . '.' . $file->extension()). This prevents path traversal and overwrites of existing files.
  • Limit file size: Set both Laravel validation ('max:2048' in KB) and PHP's upload_max_filesize and post_max_size in php.ini. Laravel validation alone does not prevent oversized files from reaching PHP memory.
  • Scan for malware (high-risk applications): Integrate ClamAV or a cloud scanning API (VirusTotal, AWS Malware Protection) for applications handling untrusted file uploads at scale.

Laravel HTTP client and server-side request forgery (SSRF)

Laravel's HTTP client wraps Guzzle and is widely used for external API calls. When URL parameters are user-controlled, the application becomes vulnerable to Server-Side Request Forgery (SSRF) — where the server makes requests to internal infrastructure on behalf of the attacker.

// VULNERABLE — user controls the URL
$url = $request->input('webhook_url');
$response = Http::get($url);  // Attacker could pass: http://169.254.169.254/latest/meta-data/

// SAFE — validate against allowlist of approved domains
$allowedDomains = ['api.stripe.com', 'api.sendgrid.com'];
$parsed = parse_url($request->input('webhook_url'));
if (!in_array($parsed['host'] ?? '', $allowedDomains)) {
    abort(422, 'Webhook URL not permitted.');
}

// ALSO SAFE — block private IP ranges explicitly
// Use a library like symfony/http-foundation's IpUtils to validate
// that the resolved IP is not in RFC1918 private ranges

Scan your application now

Detect .env exposure, debug leaks, CORS misconfigurations, missing security headers, and production hardening gaps.

Run Free Security Scan

Check Your Website Right Now

Run a free automated security scan — 75 checks in 60 seconds. No signup required.

Run Free Security Scan →