Scanning Laravel Applications with Ward and Other Security Tools
If you maintain a Laravel application in production, you already know that composer audit exists. What you may not have set up yet is a layered scanning pipeline that catches what composer audit misses — hardcoded secrets, insecure Blade output, weak crypto configuration, and runtime file-upload bypasses. This article walks through assembling exactly that pipeline using Ward, composer audit, and StackShield, with concrete commands and a realistic CI integration.
Prerequisites
- PHP 8.1+ and Composer 2.4+ (Composer 2.9+ strongly recommended)
- Laravel 10, 11, or 12 (examples tested against 12.x)
- Go 1.21+ if building Ward from source (or use the pre-built binary)
- A GitHub Actions or similar CI environment
Why composer audit Alone Is Not Enough
composer audit (available since Composer 2.4) does one thing well: it reads your composer.lock and cross-references every installed package against the PHP Security Advisories Database. Run it in any project:
# Standard audit — human-readable output
composer audit
# Machine-parseable JSON for CI artifact storage
composer audit --format=json
Composer 2.9 (released November 2025) made auditing more aggressive by defaulting audit.block-insecure to true. This means composer update will fail outright if any package has an unresolved advisory. If your builds started breaking after upgrading Composer, this is why:
{
"config": {
"audit": {
"block-insecure": true,
"block-abandoned": false
}
}
}
The limitation is scope. composer audit only knows about CVEs that have been published to the advisories database. During the May 2026 Laravel-Lang supply-chain attack, roughly six hours elapsed between initial tag-rewrite and any public advisory. In that window, composer audit returned clean results on a poisoned lockfile. A defence-in-depth strategy needs additional layers.
Ward: Static Analysis Purpose-Built for Laravel
Ward is a Go binary that understands Laravel project structure — routes, models, controllers, middleware, Blade templates, config files, .env, and Composer dependencies. It runs 42+ built-in rules grouped into categories: secrets, injection, XSS, debug, crypto, config, and auth.
Installation
# macOS (Homebrew tap)
brew install eljakani/tap/ward
# Linux — download the latest release binary
curl -L https://github.com/Eljakani/ward/releases/latest/download/ward-linux-amd64 \
-o /usr/local/bin/ward && chmod +x /usr/local/bin/ward
# Verify installation
ward --version
Basic scan
# Scan a Laravel project rooted at the current directory
ward scan .
# Output JSON for programmatic processing
ward scan . --output json > ward-results.json
# Output SARIF for GitHub Code Scanning integration
ward scan . --output sarif > ward-results.sarif
# Output Markdown for PR comments or reports
ward scan . --output markdown > ward-results.md
Ward also queries OSV.dev in real time, covering the broader open-source vulnerability ecosystem beyond the PHP Security Advisories Database — giving it wider CVE coverage than composer audit alone.
What Ward Catches That composer audit Misses
Consider these two common Laravel anti-patterns:
{{-- Unsafe: {!! !!} skips Laravel's auto-escaping, enabling XSS --}}
<h1>{!! $userInput !!}</h1>
{{-- Safe: {{ }} HTML-encodes the value by default --}}
<h1>{{ $userInput }}</h1>
Or a raw SQL query that reintroduces injection risk:
// Dangerous: direct string interpolation bypasses prepared statements
$results = DB::select("SELECT * FROM users WHERE email = '{$email}'");
// Safe: Query Builder uses PDO bindings
$results = DB::table('users')->where('email', $email)->get();
Ward's static analysis flags both patterns. composer audit is silent on both because they are not CVEs in installed packages — they are code-level vulnerabilities in your own application.
Ward Findings to Prioritise
After running a scan, focus on the high-severity findings first:
-
Secrets in code — hardcoded API keys, tokens, or credentials that should be in
.env -
Unescaped Blade output —
{!! !!}applied to request or user-controlled data - Raw SQL with interpolation — bypassing the ORM's prepared statements
-
Debug mode leaks —
APP_DEBUG=truereferences in non-test config -
Weak encryption — use of
md5()orsha1()for password hashing
StackShield: External Black-Box Scanning
StackShield takes the opposite approach to Ward. It is a zero-installation external scanner — you supply a URL, it probes your live deployment the way an attacker would, running 30+ checks without needing Composer or server access.
Use StackShield when you want a quick external audit of a staging environment, confirmation that .env is not publicly accessible, debug routes are disabled in production, or to check security headers (CSP, HSTS, X-Frame-Options).
The tradeoff: StackShield cannot read your source code and requires a live, publicly reachable deployment.
Ward vs StackShield — when to use each:
| Concern | Ward | StackShield |
|---|---|---|
| Source-level XSS patterns | Yes | No |
| Hardcoded secrets in code | Yes | No |
Exposed .env file |
Partial | Yes |
| Missing security headers | No | Yes |
| Requires live deployment | No | Yes |
| Requires source code access | Yes | No |
| Queries OSV.dev for CVEs | Yes | No |
Run Ward in CI on every push; run StackShield periodically against a live staging environment.
Enlightn as a Complement
Enlightn integrates directly with Laravel's Artisan scheduler and can email your team when new advisories appear against your installed packages:
# Install as a dev dependency
composer require --dev enlightn/laravel-security-checker
# Run the security check via Artisan
php artisan security:check
Schedule it in app/Console/Kernel.php (Laravel 10/11) or routes/console.php (Laravel 12):
// routes/console.php (Laravel 12)
Schedule::command('security:check')->daily();
Enlightn covers the same PHP Security Advisories Database as composer audit but adds scheduling, email notifications, and Artisan integration — useful for teams that do not monitor CI dashboards daily.
Critical CVEs Your Scanner Should Be Detecting
Make sure your current toolchain flags the following. If it does not, the scanner's database is out of date:
CVE-2025-27515 — File validation bypass in laravel/framework. Wildcard validation rules like files.* could be circumvented. Patched in 10.48.29, 11.44.1, and 12.1.1. If you are below these versions, composer audit should flag this immediately.
CVE-2025-54068 — Unauthenticated RCE in Livewire v3 through v3.6.3. This one bypasses the APP_KEY-signed checksum mechanism entirely — the attacker does not need your application key. It is on CISA's Known Exploited Vulnerabilities catalog. Update to Livewire v3.6.4.
CVE-2026-39976 — Authentication bypass in Laravel Passport 13.0.0–13.7.0. The TokenGuard does not verify whether a JWT sub claim belongs to a user or a client, allowing machine tokens to impersonate real users when IDs collide. Patched in Passport 13.7.1.
Verify your current versions:
# Check installed versions of affected packages
composer show laravel/framework | grep versions
composer show livewire/livewire | grep versions
composer show laravel/passport | grep versions
Integrating Ward into GitHub Actions
A complete CI job that runs composer audit and Ward in sequence:
name: Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: step-security/harden-runner@v2
with:
egress-policy: block
allowed-endpoints: |
packagist.org:443
repo.packagist.org:443
github.com:443
osv.dev:443
- uses: actions/checkout@v4
- name: Install PHP dependencies (lockfile only)
run: composer install --no-dev --optimize-autoloader
- name: Run Composer audit
run: composer audit --format=json | tee composer-audit.json
- name: Install Ward
run: |
curl -L https://github.com/Eljakani/ward/releases/latest/download/ward-linux-amd64 \
-o /usr/local/bin/ward && chmod +x /usr/local/bin/ward
- name: Run Ward scan
run: ward scan . --output sarif > ward-results.sarif
- name: Upload SARIF to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ward-results.sarif
Note the harden-runner step. This egress-policy enforcement would have contained the May 2026 Laravel-Lang supply-chain attack: blocking outbound connections to flipboxstudio.info would have silenced the credential exfiltration payload injected via autoload.files.
Common Mistakes
Not committing composer.lock. Without a committed lockfile, composer install resolves dependencies fresh each time, making your builds non-reproducible and vulnerable to tag-rewrite attacks. Always commit composer.lock.
Running composer update in CI. Use composer install in automated pipelines — it respects the lockfile. Reserve composer update for deliberate, reviewed dependency bumps in a local or staging environment.
Suppressing audit warnings. Setting COMPOSER_NO_AUDIT=1 to silence failing builds defeats the security feature. Triage each advisory; do not silence them.
Relying on Ward for runtime checks. Ward is a static analyser. It will not catch a runtime misconfiguration that only manifests under specific request conditions. Pair it with runtime anomaly detection or penetration testing for complete coverage.
Treating Ward findings as a one-time task. New rules are added as new vulnerability patterns emerge. Re-run Ward on a schedule, not just when onboarding it.
Testing and Verification
After running your scanning pipeline, verify the most critical outputs:
# Confirm composer audit exits non-zero when advisories exist
composer audit; echo "Exit code: $?"
# Parse Ward JSON output to count high-severity findings
ward scan . --output json | jq '[.findings[] | select(.severity == "high")] | length'
# Check your lockfile for the May 2026 Laravel-Lang affected packages
grep -E '"laravel-lang/(lang|attributes|http-statuses|actions)"' composer.lock
# Verify network egress from your CI runner does not reach the known IOC domain
# (check build logs for any connection to flipboxstudio.info)
For a broader view of supply-chain risk patterns and how Composer's lockfile semantics interact with tag-rewrite attacks, see the Laravel Security Guide: Supply-Chain Risks, Composer, and Application Scanning.
Summary
No single tool covers the full attack surface of a Laravel application. composer audit is fast and built-in — run it on every CI push. Ward brings static analysis that understands Laravel's structure and queries a broader vulnerability database. StackShield gives you an external attacker's perspective on a live deployment. Enlightn ties advisory checking into Laravel's scheduler for ongoing team notifications. Stack them, automate them, and do not suppress their warnings.
If you need Laravel development in Mumbai, Mumbai Web Designer builds production-grade Laravel applications.
Top comments (0)