Most developers run composer audit once, see a stack of advisory blocks and the word "high" in red, and close the terminal without changing anything. That is the worst outcome, because the command just did its job. composer audit compares every package you have installed against the Packagist security advisories database (which aggregates the FriendsOfPHP and GitHub advisory data) and tells you exactly which versions carry known CVEs. The fix is almost always a targeted composer update to a patched version, sometimes a constraint bump in composer.json, and occasionally a tracked, dated mitigation while you wait on a breaking upgrade.
What does each field in the report actually mean?
Run it from your project root. By default it audits the packages you currently have installed, so the results reflect the exact versions in your vendor directory, not whatever your constraints theoretically allow.
composer audit
# Focus on what ships to production; skip dev-only tooling
composer audit --no-dev
# Machine-readable output for a CI step or a dashboard
composer audit --format=jsonEach advisory is printed as a small table, and the fields tell you everything you need before touching code:
- Package — the vulnerable dependency, e.g. guzzlehttp/guzzle. It may be a direct dependency or a transitive one you have never heard of.
- Severity — low, medium, high, or critical. Triage high and critical first, but do not ignore the rest; severity reflects the advisory, not your specific exposure.
- Advisory ID — Composer's own identifier in the form PKSA-xxxx-xxxx-xxxx. This is the value you reference if you later have to ignore an advisory.
- CVE — the public identifier, e.g. CVE-2022-31090. Search it to understand the real-world impact and whether your usage is even affected.
- Title and URL — a one-line summary and a link to the full advisory. Read it before you assume the worst.
- Affected versions — the vulnerable constraint, e.g. >=7,<7.4.5. If your installed version falls inside this range, you are exposed.
- Reported at — when the advisory was published, which is useful when you are deciding how urgently to react.
The same command also flags abandoned packages. Those carry no CVE, but an unmaintained package will never receive a future patch, so treat each one as a replacement task on your backlog rather than a warning to silence.
How do I fix a vulnerable package?
My default move is to update the named package to a version outside the affected range. If the constraint in composer.json already allows the patched version, a targeted update is all you need, and the lock file gets a single reviewable change:
# Update just the affected package to its highest allowed patched version
composer update guzzlehttp/guzzle
# If the fix lives deeper in the tree, let Composer move the
# dependencies of that package too (-W is the short alias)
composer update guzzlehttp/guzzle --with-all-dependenciesTwo outcomes are common, and both have a clean diagnosis. If the update does nothing, your constraint in composer.json is pinning you below the patch — bump it (for example "^7.4" to "^7.4.5") and update again. If the package is transitive and refuses to move, something upstream is holding it back. Do not guess; ask Composer directly.
# Who pulled this package in, and through what chain?
composer why guzzlehttp/guzzle
# Why can't I install the version I want? Composer names the blocker.
composer why-not guzzlehttp/guzzle 7.4.5composer why-not is the most useful command in this whole workflow. It tells you in plain text that some parent package pins guzzle to an old major, which is why even -W cannot lift you to a safe version. Now you know the real fix is upgrading the parent, not the leaf.
A red audit report is not a verdict on your project. It is a to-do list with the answers already filled in.
What if I genuinely cannot upgrade yet?
Sometimes the only patched version sits behind a breaking major bump you cannot ship this sprint. The honest move is a temporary, tracked mitigation, never a silent one. There is no command-line flag to skip a single advisory in a run; the supported mechanism is the audit.ignore key in composer.json, which gets committed and code-reviewed. Composer itself points you here when an install is blocked by an advisory:
{
"config": {
"audit": {
"ignore": {
"CVE-2022-31090": "TICKET-482: blocked on guzzle 6 to 7 major upgrade. Re-check by 2026-07-15."
}
}
}
}Note that the value is a reason string, not just true. Every ignore entry should carry a ticket reference and a revisit date. An undocumented ignore is how a critical CVE quietly lives in production for two years. This is interim cover to keep the build green while you do the real work — it is not a fix, and a reviewer should treat any new ignore line as a red flag that needs a date attached. You can key the ignore on either the CVE or the Composer advisory ID (PKSA-...).
How do I stop new advisories from sneaking back in?
Auditing once tells you nothing next week. The value comes from running it on every build, so that a newly disclosed advisory against a package you already ship fails the pipeline the same day it lands. Add it as a step in CI alongside your other checks. If you are setting that up, my walkthrough on a CI/CD pipeline with GitHub Actions for Laravel shows where this fits, and it pairs with the broader Laravel security checklist for the rest of your hardening.
# CI step: a non-zero exit on any advisory fails the build.
# --no-dev keeps the gate focused on production dependencies.
composer audit --no-dev
# JSON for a security dashboard, gated separately if you want soft reporting
composer audit --no-dev --format=jsoncomposer audit exits non-zero when it finds advisories, so a bare invocation is already a working CI gate with no scripting required. If you want Composer to refuse to install a vulnerable package in the first place, the roave/security-advisories metapackage is a separate, complementary tool that blocks installs at resolve time rather than reporting after the fact.
Once you stop fearing the output, the loop is short: read the affected-versions field to confirm you are actually exposed, run composer update with -W to pull the fix through transitive dependencies, lean on composer why-not when something refuses to move, and reserve audit.ignore for breaking upgrades that have a dated ticket behind them. Wire it into CI, and the report stops being a once-a-year scare and becomes the boring green check it should be.

