How to Security Audit a Laravel Application: A Practical Guide
A step-by-step guide to auditing the security of a Laravel application. Covers dependency scanning, configuration review, external scanning, code review patterns, and how to prioritize findings.
A security audit does not need to be a six-figure engagement. For most Laravel applications, a structured self-audit catches the majority of exploitable issues. This guide walks through the process step by step.
Phase 1: Dependency Scanning (15 minutes)
Start with what is already known to be vulnerable.
Run composer audit
composer audit
This checks every package in your composer.lock against the PHP Security Advisories Database. Any result here is a known vulnerability with a public exploit path.
Prioritise by severity:
- Critical/High: Update immediately. These have known exploits.
- Medium: Update this sprint. These are exploitable under specific conditions.
- Low: Update when convenient. These are theoretical or require unusual configurations.
If you cannot update a package (breaking changes, abandoned package), document the risk and check whether the specific vulnerability applies to your usage.
Check for abandoned packages
composer outdated --direct
Packages that have not been updated in over a year may have unpatched vulnerabilities that are not in the advisories database yet. Consider replacing them.
For a deeper dive on managing PHP dependency vulnerabilities, see our composer audit guide.
Phase 2: Configuration Review (30 minutes)
Configuration mistakes are the most common cause of Laravel security incidents. They are also the easiest to find and fix.
Environment settings
Check your production .env (or environment variables):
| Setting | Expected Value | Risk if Wrong |
|---|---|---|
| APP_DEBUG | false | Exposes credentials, stack traces, APP_KEY |
| APP_ENV | production | May disable security middleware |
| SESSION_SECURE_COOKIE | true | Session cookies sent over HTTP |
| SESSION_HTTP_ONLY | true | JavaScript can read session cookies |
| SESSION_SAME_SITE | lax or strict | CSRF via cross-site requests |
CORS configuration
Open config/cors.php and check:
// DANGEROUS: any site can call your API as the user
'allowed_origins' => ['*'],
'supports_credentials' => true,
// SAFE: explicit origin list
'allowed_origins' => ['https://yourdomain.com', 'https://app.yourdomain.com'],
'supports_credentials' => true,
Wildcard origins with credentials enabled is the single most exploitable CORS misconfiguration.
CSRF exceptions
Open app/Http/Middleware/VerifyCsrfToken.php and check the $except array. Every URL listed here is unprotected from CSRF. Webhook endpoints from Stripe or GitHub are fine. Application routes that change state are not.
Telescope, Horizon, and debug tools
If installed in production, these must be gated behind authentication:
Gate::define('viewTelescope', fn ($user) => $user->is_admin);
An ungated Telescope instance exposes every database query, every request payload, and every job in your queue.
Phase 3: External Scanning (15 minutes)
Check what your application looks like from the outside.
Automated external scan
Run a free StackShield scan to check for:
- Exposed .env files
- Debug mode detection
- Missing security headers
- SSL/TLS configuration issues
- Directory listing
- Publicly accessible Telescope/Ignition/Horizon
- Subdomain takeover risks
- Session cookie security flags
This catches the configuration issues from Phase 2 that have actually made it to production, plus infrastructure-level issues you cannot see from inside the application.
Manual checks
A few things to verify manually:
# Check if .env is accessible
curl -sI https://yourdomain.com/.env | head -1
# Check security headers
curl -sI https://yourdomain.com | grep -iE "(strict-transport|x-frame|x-content-type|content-security|referrer-policy)"
# Check for directory listing
curl -s https://yourdomain.com/storage/ | head -5
Phase 4: Code Review (2-8 hours)
This is the most time-intensive phase but catches vulnerabilities that automated tools miss.
XSS vectors
Search your Blade templates for unescaped output:
grep -r '{!!' resources/views/ | grep -v '.svn'
Every {!! !!} is a potential XSS vulnerability. For each one, verify the data source is trusted or properly sanitized. See our XSS prevention guide.
SQL injection
Search for raw queries:
grep -rn 'DB::raw\|->whereRaw\|->selectRaw\|->orderByRaw\|->havingRaw' app/
Each raw query that includes user input without bindings is a SQL injection vector. Use parameterized queries:
// Vulnerable
DB::select("SELECT * FROM users WHERE email = '$email'");
// Safe
DB::select('SELECT * FROM users WHERE email = ?', [$email]);
Mass assignment
Check your models for unprotected mass assignment:
grep -rn 'protected \$guarded = \[\]' app/Models/
$guarded = [] means every column is mass-assignable. Use $fillable to explicitly list allowed fields instead.
Authorization checks
For every controller action that accesses or modifies data, verify there is an authorization check. Look for:
- Missing
$this->authorize()calls - Missing Policy checks
- Direct model access without ownership verification (e.g.,
User::find($id)without checking the requesting user owns that record)
File upload handling
Search for file upload handling and verify:
- MIME type validation (not just extension)
- File size limits
- Storage location is not publicly accessible
- Filenames are randomized, not user-controlled
Phase 5: Prioritise and Fix
After the audit, you will have a list of findings. Prioritise them:
Fix immediately (this deployment):
- Exposed .env file
- Debug mode enabled
- Known dependency vulnerabilities (critical/high)
- SQL injection
- Ungated debug tools
Fix this week:
- Missing security headers
- Insecure session configuration
- CORS misconfiguration
- XSS in user-facing pages
Fix this sprint:
- Mass assignment issues
- Missing rate limiting
- Authorization gaps
- Medium-severity dependency updates
Track for next quarter:
- Content Security Policy implementation
- Comprehensive authorization review
- Penetration test
Automate What You Can
Manual audits catch problems. Automated monitoring prevents them from recurring.
- Dependencies: Run
composer auditin CI/CD. Fail the build on critical advisories. - External surface: Use StackShield to scan continuously after every deployment.
- Static analysis: Add PHPStan or Psalm to your CI pipeline with security-focused rules.
A quarterly manual audit combined with continuous automated monitoring is the most practical approach for most Laravel teams.
Start with a free StackShield scan to baseline your current security posture.
Is your Laravel app exposed right now?
34% of Laravel apps we scan have at least one critical issue. Most teams don't find out until something breaks. Our free scan checks your live application in under 60 seconds.
Frequently Asked Questions
What is a Laravel security audit?
A Laravel security audit is a systematic review of your application's security posture. It covers dependency vulnerabilities (composer audit), configuration review (debug mode, session settings, CORS), external attack surface scanning (exposed files, open ports, missing headers), and code review for common vulnerability patterns (XSS, SQL injection, mass assignment).
How long does a Laravel security audit take?
A basic automated audit (dependency scan + external scan + configuration review) takes 30 to 60 minutes. A thorough audit including manual code review of authentication, authorization, and input handling typically takes 2 to 5 days depending on application size. Most teams benefit from running automated checks continuously and scheduling manual reviews quarterly.
Can I automate a Laravel security audit?
Partially. Dependency scanning (composer audit), external attack surface scanning (StackShield), and static analysis (PHPStan, Psalm with security rules) can all run automatically in CI/CD. Manual code review for business logic flaws, authorization bypasses, and complex injection patterns still requires human expertise.
What is the difference between a security audit and a penetration test?
A security audit reviews configuration, code, and dependencies against known best practices. A penetration test actively attempts to exploit vulnerabilities to demonstrate real-world impact. Audits are broader and cheaper. Pentests are deeper and more expensive. Most Laravel teams should audit continuously and pentest annually.
Related Articles
Laravel Session Security: Cookies, Hijacking & config/session.php
A deep dive into Laravel session security. Learn how cookie flags, session drivers, and config/session.php settings protect against hijacking, fixation, and sidejacking attacks.
SecurityAutomated Security Testing in Laravel CI/CD Pipelines
How to add security gates to your Laravel CI/CD pipeline with GitHub Actions. Covers dependency scanning, static analysis, secret detection, and automated security monitoring.
SecurityLaravel Content Security Policy: Configure CSP Without Breaking Your App
Only 22% of Laravel apps have a Content Security Policy. Learn how to implement CSP with spatie/laravel-csp, handle Livewire and Vite nonces, and avoid the mistakes that break production.
Compare StackShield
Security Checklists
Laravel Production Deployment Security Checklist
A comprehensive security checklist for deploying Laravel applications to production. Covers environment config, server hardening, access control, and monitoring.
20 itemsLaravel API Security Checklist
Secure your Laravel API endpoints against common vulnerabilities. Covers authentication, input validation, rate limiting, and response security.
Stay Updated on Laravel Security
Get actionable security tips, vulnerability alerts, and best practices for Laravel apps.