DEV Community

Cover image for Scanning Laravel Applications with Ward and Other Security Tools
Sumeet Shroff
Sumeet Shroff

Posted on

Scanning Laravel Applications with Ward and Other Security Tools

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
Enter fullscreen mode Exit fullscreen mode

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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:

  1. Secrets in code — hardcoded API keys, tokens, or credentials that should be in .env
  2. Unescaped Blade output{!! !!} applied to request or user-controlled data
  3. Raw SQL with interpolation — bypassing the ORM's prepared statements
  4. Debug mode leaksAPP_DEBUG=true references in non-test config
  5. Weak encryption — use of md5() or sha1() 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
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.

Read the full guide

Top comments (0)