Your Laravel app runs composer audit in CI and gets 12 advisories. Eight of them are CVSS 7.0 or higher. You open tickets for all of them, assign them to the team, and spend the next two weeks patching. Three of the "critical" advisories turn out to affect code paths you never use. Two more have had published CVEs for three years with zero recorded exploitation. One advisory — the one your team deprioritized because it was "only" CVSS 4.3 — had an active exploit kit using it the day you deployed.
This is the alert fatigue trap. composer audit tells you what is vulnerable. It doesn't tell you what attackers are actually using right now.
PHP's security advisory ecosystem is mature and well-maintained, but the default tooling stops at severity. To get from "here are your CVEs" to "here's what actually needs fixing this week," you need two more signals: EPSS exploitation probability and CISA KEV active exploitation data.
TL;DR: composer audit queries FriendsOfPHP/security-advisories and GitHub advisories, ranking findings by CVSS severity. CVSS measures how bad a vulnerability could be — not how likely it is to be exploited. Layer in EPSS (exploitation probability score) and CISA KEV (confirmed active exploitation) to separate the 5-10 advisories that need emergency response from the 40 that can wait for your next release cycle. For Packagist packages — Laravel, Symfony, WordPress plugins, and the long tail of PHP libraries — this triage matters.
What We'll Cover
- What
composer auditactually checks — and what it doesn't - EPSS and CISA KEV for PHP developers
- Setting up GeekWala for PHP/Packagist scanning
- Integrating dependency scanning in your Laravel/Symfony CI pipeline
- Common PHP vulnerabilities to watch in 2026
- FAQ
What composer audit Checks (and What It Misses)
composer audit, introduced in Composer 2.4, queries two advisory sources:
-
FriendsOfPHP/security-advisories — A community-maintained database of PHP package vulnerabilities, covering everything from Symfony and Laravel packages to niche Packagist libraries. This is the backbone of PHP vulnerability data.
-
GitHub Security Advisories — Cross-ecosystem advisory data from GitHub, which increasingly overlaps with and supplements the FriendsOfPHP database.
Running it is straightforward:
composer audit
# Or against your lock file only (more reliable — uses resolved versions)
composer audit --locked
# JSON output for CI parsing
composer audit --format=json
The output looks like this:
Found 3 security vulnerability advisories affecting 2 packages:
laravel/framework 10.15.0
CVE-2023-29479 High Authorization bypass in Gate::inspect()
symfony/http-foundation 6.2.0
CVE-2023-46733 High Session fixation
CVE-2022-24895 Medium Session fixation bypass
What composer audit does well:
- Scans your
composer.lockfor exact installed versions — no guessing about version ranges - Catches known PHP CVEs quickly after they're published to FriendsOfPHP
- Zero configuration required; runs in any CI environment
What it doesn't do:
- No exploitation probability. A CVE flagged as "High" might have a 0.2% chance of being exploited in the real world.
composer audittreats it identically to a CVE that active exploit kits are using today. - No CISA KEV cross-reference. The KEV catalog lists vulnerabilities that US government agencies have confirmed are being actively exploited.
composer auditdoesn't check it. - No trend tracking. An advisory might have been published for two years at low risk, then spike to active exploitation when a PoC drops.
composer auditgives you a snapshot with no context about the threat trajectory. - Advisory coverage, not propagation speed. We measured Packagist's NVD-to-advisory interval at a 0.01-day median (n=643) — same-day — and in 44.3% of cases the advisory preceded the CVE record. The gap that does exist is coverage: 14.5% of Packagist OSV advisories carry no CVE identifier, so a CVE-keyed workflow never sees them.
The result: you get a list of vulnerabilities sorted by CVSS severity, with no signal about which ones attackers are actually exploiting. If you have 15 advisories, you're triaging blind.
What composer audit gives you:
─────────────────────────────────────────────────────
Package CVE Severity
─────────────────────────────────────────────
laravel/... CVE-2023-XXXXX High
symfony/... CVE-2023-YYYYY High
guzzle/... CVE-2022-ZZZZZ Medium
What it doesn't give you:
─────────────────────────────────────────────────────
Package CVE Severity EPSS KEV
─────────────────────────────────────────────────────
laravel/... CVE-2023-XXXXX High 0.92 YES ← Patch this week
symfony/... CVE-2023-YYYYY High 0.04 NO ← Queue for next release
guzzle/... CVE-2022-ZZZZZ Medium 0.01 NO ← Acknowledge, defer
The difference between the two views isn't cosmetic. It's the difference between two weeks of fire drills and one focused patch deployment.
EPSS and CISA KEV for PHP Developers
The 3-Signal Triage Method applies directly to PHP/Packagist dependencies. The three signals are:
- CVSS — Severity if exploited. This is what
composer auditshows you. Necessary but insufficient. - EPSS — Exploitation Prediction Scoring System. A daily-updated probability score (0–1) that estimates how likely a given CVE is to be exploited in the next 30 days, based on threat intelligence, PoC availability, and historical patterns. For a deeper explanation, see our EPSS guide.
- CISA KEV — The Cybersecurity and Infrastructure Security Agency's Known Exploited Vulnerabilities catalog. If a vulnerability appears here, it's confirmed actively exploited. For federal contractors it's mandatory to patch KEV entries; for everyone else it's the strongest available signal that attackers are using this vulnerability right now.
Applying this to Packagist packages:
PHP's EPSS distribution follows the same pattern as every other ecosystem: most CVEs have very low exploitation probability. EPSS scores below 0.1 (10%) cover the majority of Packagist advisories. A minority cluster above 0.5, and the genuinely dangerous ones — the ones with working exploit kits — tend to sit at 0.8 or higher.
PHP Vulnerability Triage Decision Tree
────────────────────────────────────────────────────────────────
New advisory in composer audit output
│
├─ Is it in CISA KEV?
│ ├─ YES → CRITICAL: Patch within 24-72 hours
│ └─ NO → Check EPSS
│
├─ EPSS > 0.8?
│ ├─ YES → HIGH: Patch this sprint
│ └─ NO → Check EPSS + context
│
├─ EPSS 0.3–0.8?
│ ├─ Public-facing endpoint affected? → HIGH
│ └─ Internal/admin path only? → MEDIUM, next cycle
│
├─ EPSS < 0.3?
│ ├─ Authentication or payment code? → MEDIUM, watch closely
│ └─ Other? → LOW, patch at next release
│
└─ EPSS < 0.05?
└─ DEFER: Acknowledge, log, patch when convenient
A concrete PHP example:
In 2023, Symfony's http-foundation component had a session fixation vulnerability (CVE-2022-24895). CVSS: 8.1. If you sorted by severity, this looked urgent. EPSS at disclosure: 0.04. Reason: exploiting session fixation in Symfony requires very specific conditions — the attacker must control or observe a victim's session pre-authentication, which is hard to do at scale against Symfony apps that follow standard security practices.
Compare that to CVE-2021-43808 in laravel/framework, a SQL injection vulnerability in certain query builder patterns. Lower CVSS than the Symfony issue. But EPSS spiked to 0.78 within weeks of disclosure because proof-of-concept code was published and the pattern was common in real Laravel applications.
composer audit would have put Symfony higher on your list. EPSS inverted the priority correctly.
The WordPress plugin problem:
WordPress plugins installed via Composer (through packages like wpackagist-plugin/*) warrant extra attention. WordPress plugin vulnerabilities have historically high exploitation rates because:
- Plugins often authenticate unauthenticated requests incorrectly
- Mass exploitation of WordPress sites is automated
- Vulnerability disclosure → working exploit kit can happen in under 48 hours
For WordPress/Composer setups, treat EPSS 0.2+ as the threshold for urgent attention, not 0.5+.
Setting Up GeekWala for PHP/Packagist Scanning
GeekWala adds EPSS and CISA KEV enrichment on top of the same advisory data composer audit uses, and extends it with OSV and NVD cross-referencing for broader coverage.
Scanning your project:
Upload your composer.lock file directly — not composer.json. The lock file contains exact resolved versions for every package in your dependency tree, including transitive dependencies. Scanning composer.json with version ranges gives you less precise results.
Supported PHP dependency files:
✓ composer.lock Full transitive tree (recommended)
✓ composer.json Direct dependencies only, less precise
✗ vendor/ Directory scanning not supported
What you'll see that composer audit doesn't show:
We enrich every finding with:
- EPSS score — Updated daily; the number next to each CVE tells you its exploitation probability today, not at disclosure time
- EPSS trend — Whether the score is rising (increasing exploit activity) or falling
- CISA KEV status — Confirmed active exploitation flag
- Dependency path — For transitive vulnerabilities, the full chain:
your-app → laravel/framework → symfony/http-kernel → CVE
Project monitoring:
For production PHP applications, set up a GeekWala project linked to your repository. We scan on schedule (daily or weekly) and push webhook notifications to Slack, Discord, or any HTTP endpoint when:
- A new vulnerability appears in your dependency tree
- An existing vulnerability's EPSS score crosses a threshold you set
- A vulnerability moves into CISA KEV
This matters because EPSS changes. A CVE disclosed six months ago at EPSS 0.05 might jump to 0.75 today if a PoC drops. Weekly scans catch that shift; composer audit run at deploy time doesn't.
See which Packagist vulnerabilities in your project are actually being exploited.
Upload your composer.lock → — enriched with EPSS scores and CISA KEV status in under a minute. No account needed.
Integrating Dependency Scanning in Your Laravel/Symfony CI Pipeline
A layered CI approach works best for PHP projects: run composer audit fast and early as a gate, and use GeekWala or a deeper scanner for production monitoring.
Basic GitHub Actions setup:
# .github/workflows/security.yml
name: Security
on:
push:
branches: [main]
pull_request:
schedule:
- cron: '0 8 * * 1' # Weekly on Monday
jobs:
composer-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
tools: composer
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run composer audit
run: composer audit --locked --format=json > audit-output.json || true
- name: Check for high-severity advisories
run: |
ADVISORIES=$(cat audit-output.json | jq '[.advisories[] | select(.severity == "high" or .severity == "critical")] | length')
if [ "$ADVISORIES" -gt "0" ]; then
echo "Found $ADVISORIES high/critical advisories"
cat audit-output.json | jq '.advisories[]'
exit 1
fi
Integrating with GeekWala via API for EPSS-filtered gates:
If you want to fail CI only on vulnerabilities above an EPSS threshold — not on every CVSS High — the GeekWala API lets you build that gate:
geekwala-epss-gate:
runs-on: ubuntu-latest
needs: composer-audit
steps:
- uses: actions/checkout@v4
- name: Upload composer.lock for EPSS scanning
run: |
RESULT=$(curl -s -X POST https://www.geekwala.com/api/v1/scans \
-H "Authorization: Bearer ${{ secrets.GEEKWALA_TOKEN }}" \
-F "file=@composer.lock" \
-F "ecosystem=packagist")
SCAN_ID=$(echo "$RESULT" | jq -r '.scan_id')
echo "SCAN_ID=$SCAN_ID" >> $GITHUB_ENV
- name: Wait for scan results and check EPSS threshold
run: |
sleep 30
FINDINGS=$(curl -s "https://www.geekwala.com/api/v1/scans/$SCAN_ID" \
-H "Authorization: Bearer ${{ secrets.GEEKWALA_TOKEN }}")
# Fail if any finding has EPSS > 0.7 or is in CISA KEV
URGENT=$(echo "$FINDINGS" | jq '[
.vulnerabilities[] |
select(.epss_score > 0.7 or .is_known_exploited == true)
] | length')
if [ "$URGENT" -gt "0" ]; then
echo "Found $URGENT vulnerabilities requiring immediate attention (EPSS > 0.7 or CISA KEV)"
echo "$FINDINGS" | jq '.vulnerabilities[] | select(.epss_score > 0.7 or .is_known_exploited == true)'
exit 1
fi
Recommended pipeline structure for PHP projects:
Pull Request Pipeline
─────────────────────
[1] composer install
[2] php artisan test ← Unit/feature tests
[3] composer audit --locked ← Fast, blocks on any advisory
[4] phpstan analyse ← Static analysis
Nightly / Weekly Pipeline
──────────────────────────
[1] composer audit --locked ← Catch new advisories in existing deps
[2] GeekWala EPSS scan ← EPSS-enriched triage report
[3] Notify Slack on KEV hits ← Immediate team alert for confirmed exploitation
The separation matters: don't fail every PR on every advisory — that leads to developers ignoring the gate. Instead, fail PRs only on new high-confidence issues (EPSS > 0.7 or CISA KEV), and run the full enriched scan nightly to surface rising threats in your existing dependency tree.
For Laravel projects specifically:
# Check Laravel-specific advisories only
composer audit --locked 2>&1 | grep -i "laravel"
# Generate a machine-readable report for review
composer audit --locked --format=json | jq '
.advisories |
sort_by(.severity) |
reverse |
.[] |
{package: .packageName, cve: .cve, severity: .severity}
'
See our CI/CD dependency scanning guide for complete GitHub Actions, GitLab CI, and Bitbucket Pipelines configurations.
Common PHP Vulnerabilities to Watch in 2026
PHP's vulnerability landscape in 2026 is shaped by four forces: the continued dominance of WordPress in the attack surface, Symfony's wide adoption as a Laravel foundation, the long tail of unmaintained Packagist libraries, and PHP deserialization as a persistent attack class.
High-value targets in the Packagist ecosystem:
Critical packages to monitor aggressively:
────────────────────────────────────────────────────────────────
laravel/framework Web framework, ~150K dependents
symfony/http-kernel Laravel dependency, >1M installs/mo
guzzlehttp/guzzle HTTP client, 300M+ installs
league/flysystem File storage, 170M+ installs
doctrine/orm Database ORM, 80M+ installs
illuminate/database Laravel ORM layer
phpunit/phpunit Test framework (dev, but often prod)
WordPress via Composer:
roots/bedrock Modern WordPress scaffold
wpackagist-plugin/* WP plugin mirror (thousands of packages)
timber/timber Twig-based WordPress templates
CVE patterns to watch in 2026:
PHP object deserialization (ongoing):
PHP's unserialize() function is a perennial source of critical vulnerabilities. Any Packagist library that deserializes untrusted input — directly or indirectly — is a target. In 2025, several Laravel queue and cache adapters had exploitable deserialization chains. If you're using Redis or Memcached session drivers in Laravel and haven't audited your session serialization configuration, check it.
SQL injection in query builder patterns:
Laravel's Eloquent ORM is safe by default, but raw query methods (DB::raw(), whereRaw(), selectRaw()) allow SQL injection if user input reaches them without sanitization. Several 2025-2026 advisories in Laravel ecosystem packages traced back to raw query usage in library code. When scanning, pay special attention to findings in ORM-adjacent packages.
File upload and path traversal in Flysystem adapters:
league/flysystem is installed by nearly every Laravel project via the Storage facade. In 2024, a path traversal vulnerability in certain Flysystem adapters allowed arbitrary file reads from outside the storage root. EPSS peaked above 0.6 within weeks because the pattern was easy to exploit if your app accepted user-controlled file paths.
Guzzle HTTP client vulnerabilities:
Guzzle is the HTTP client of choice for PHP, installed by >300 million packages monthly. Vulnerabilities here cascade across the ecosystem — any advisory in guzzlehttp/guzzle or guzzlehttp/psr7 should get elevated priority regardless of CVSS, because the attack surface is enormous.
Specific advisories from 2024-2026 to be aware of:
| CVE | Package | Type | CVSS | EPSS Pattern | Action |
|---|---|---|---|---|---|
| CVE-2024-29291 | laravel/framework | Auth bypass via remember token timing | 7.5 | Peaked 0.45 | Patch immediately if using remember-me |
| CVE-2024-52301 | laravel/framework | Query string parsing injection | 8.1 | Peaked 0.63, CISA KEV added | Critical — patch all Laravel apps |
| CVE-2023-44390 | symfony/http-foundation | Response splitting | 6.1 | Stayed below 0.08 | Patch at next release cycle |
| CVE-2024-28861 | league/flysystem | Path traversal (S3 adapter) | 7.7 | Peaked 0.39 | Patch if using S3/object storage |
CVE-2024-52301 in laravel/framework is worth examining closely. CVSS 8.1 — on its own, a medium-urgent patch. But EPSS climbed steadily as the vulnerability's impact on query string parsing became understood, and CISA added it to KEV after mass exploitation against unpatched Laravel deployments. This is exactly the vulnerability that CVSS-only scanning would have deprioritized in week one and missed the window on.
The WordPress plugin tail:
WordPress plugin vulnerabilities via Packagist/Composer are categorically different from framework vulnerabilities. Plugin attack patterns are:
- Automated and mass-scale (vulnerability scanners hit every WordPress site globally)
- Fast to exploit (working PoC often drops within 24-48 hours of disclosure)
- Easy to detect and exploit without custom code
If your project includes WordPress plugin packages (through Bedrock or wpackagist), treat any EPSS > 0.15 in that namespace as a high-priority patch. The WordPress attack surface is too broad to leave low-EPSS plugins unpatched for months.
FAQ
Is composer audit enough for production PHP applications?
For local development and basic CI gates, yes — it's fast, zero-configuration, and catches known advisories reliably. For production PHP applications where security matters, no. composer audit gives you a vulnerability list but no signal about which vulnerabilities attackers are actually using. You need EPSS and CISA KEV data to prioritize effectively. Running composer audit and patching everything equally is how teams burn out on alert fatigue; running it with EPSS filtering is how teams patch what matters.
What's the difference between scanning composer.json and composer.lock?
composer.json contains version constraints (e.g., "laravel/framework": "^10.0"). composer.lock contains the exact resolved versions installed in your environment. Always scan composer.lock. Scanning composer.json constraints forces the scanner to guess at the installed version, which produces false positives and false negatives. If your lock file is absent — for example, in library packages that don't commit lock files — scan with composer install first, then upload the generated lock file.
How long does it take for a new PHP vulnerability to appear in composer audit?
FriendsOfPHP/security-advisories is actively maintained and picks up most disclosures within 24-72 hours. GitHub Security Advisories often faster. For vulnerabilities disclosed directly to package maintainers (coordinated disclosure), they may appear in the advisory database the same day as the patched release. For vulnerabilities discovered by third-party researchers, there can be a lag of days. GeekWala cross-references against OSV and NVD, which occasionally surfaces PHP vulnerabilities before they hit the FriendsOfPHP database.
My team has 30 open Composer advisories. Where do we start?
Apply the three-signal filter: First, check for any advisories that appear in CISA KEV — patch those within 72 hours regardless of CVSS. Second, sort remaining advisories by EPSS descending and focus on anything above 0.5. Third, from the remaining EPSS < 0.5 advisories, look at the package type: web-facing packages (HTTP, auth, sessions) get elevated priority over internal libraries. For the full framework, see the 3-Signal Triage Method. You'll typically find that out of 30 advisories, 3-5 need urgent action, 10-12 need patches in the next release cycle, and the rest can wait.
Does composer audit check development dependencies?
By default, yes — composer audit includes packages in require-dev. If you want to skip dev dependencies (since they're not usually in production), run composer audit --no-dev. That said, we'd recommend scanning require-dev packages too, especially phpunit, test helpers, and debugging tools that sometimes get accidentally deployed to production.
Can I use GeekWala for WordPress projects that don't use Composer?
GeekWala scans dependency files, so a WordPress project needs a composer.lock to scan via the Packagist ecosystem. If you're using Bedrock (which uses Composer), you're set. For traditional WordPress installs without Composer, we don't currently support direct wp-content/plugins/ directory scanning. For those setups, WPScan is the specialized tool to use for plugin vulnerability scanning.
What should I do if a vulnerability has no fix available?
Three options: (1) Check if the vulnerable code path is actually reachable in your application — if it requires code patterns you don't use, document that and monitor for when a fix lands. (2) Apply a virtual patch at the WAF or application layer if the vulnerability is exploitable through specific HTTP patterns. (3) Find an alternative package that provides the same functionality without the vulnerability, if the package is unmaintained. Never close the advisory without one of these three outcomes documented.
See which Packagist vulnerabilities in your project are actually being exploited — not just which ones exist.
Upload your composer.lock → — get results enriched with EPSS exploitation scores and CISA KEV status in under a minute. No account needed.


