Security 14 min read

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.

Matt King
Matt King
April 26, 2026
Last updated: April 21, 2026
How to Security Audit a Laravel Application: A Practical Guide

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 audit in 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.

Free security check

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.

18% have debug mode on
72% missing security headers
12% have exposed .env
Scan My App Free No signup required. Results in 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.

Stay Updated on Laravel Security

Get actionable security tips, vulnerability alerts, and best practices for Laravel apps.