# 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.

**Author:** Matt King | **Published:** April 26, 2026 | **Category:** Security

---

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](https://github.com/FriendsOfPHP/security-advisories). 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](/blog/composer-vulnerability-management).

---

## 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:

```php
// 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:

```php
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](https://stackshield.io/free-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:

```bash
# 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:

```bash
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](/fix/xss-prevention).

### SQL injection

Search for raw queries:

```bash
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:

```php
// 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:

```bash
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](https://stackshield.io) 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](https://stackshield.io/free-scan) to baseline your current security posture.

---

## 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.

