Cybersecurity

Composer Audit: Finding Vulnerable PHP Dependencies Before They Reach Production

How composer audit works, what changed in Composer 2.10 and 2.11 (policy, malware blocking, cooldown), and how to use it in CI and on servers.

Published
Reading time
6 min read

A typical Laravel, Symfony, Drupal or Magento application runs far more third-party code from Composer packages than code its own team wrote. Composer can check those packages against published security advisories with one command, composer audit, and recent releases go further by blocking known-vulnerable and malicious versions during updates. This guide explains what the audit checks, what changed in 2026, and how to make it part of your routine. All behaviour described is from the official Composer documentation and changelog.

Which Composer do you have?

Features depend heavily on the version, and the newest releases came out this week: Composer 2.11.0 on 29 October 2026 and 2.11.1 on 30 October 2026.

composer --version
command -v composer
  • 2.4 (2022) added composer audit, plus an automatic audit after update and require (skip it with --no-audit).
  • 2.9 (November 2025) started blocking package versions with known security advisories during update and require, on by default.
  • 2.10 (May 2026) introduced the policy configuration block, which replaces and deprecates most of the old audit settings, added detection and blocking of package versions flagged as malware (blocking at install time by default), and simplified the audit exit code to 0 for no issues and 1 for anything that failed.
  • 2.11 (October 2026) added a cooldown policy and the --direct audit option.

Distribution packages lag behind: Debian 13 (trixie) packages Composer 2.8.8 and Debian 12 (bookworm) 2.5.5, so neither has advisory blocking or the policy block. The documentation also notes that composer self-update is not available when Composer was installed by an operating-system package manager. If you want the current behaviour, install Composer from getcomposer.org and update it with composer self-update.

What composer audit checks

By default the audit sends the names of your installed packages to the Packagist.org security advisory API (or to other repositories you configured) and reports:

  • Security advisories affecting the installed versions. Packagist aggregates advisories from sources such as the GitHub Advisory Database and the FriendsOfPHP security-advisories project, and returns a CVE ID and severity when available.
  • Abandoned packages, whose maintainers have stopped working on them.
  • Malware-flagged versions (Composer 2.10 and later).
composer audit                    # installed packages in vendor/
composer audit --locked           # composer.lock, even without vendor/
composer audit --no-dev           # skip require-dev packages
composer audit --format=json      # table (default), plain, json or summary
composer audit --abandoned=report # ignore, report or fail

The audit cannot see what is not a Composer package. PHP itself, PHP extensions from your distribution, and WordPress plugins installed from the dashboard are outside its view; they need operating-system updates and the platform's own update tools.

Fixing what it finds

Most findings are fixed by updating the affected package within the constraints your project already allows:

composer update vendor/package --with-dependencies
composer audit
composer outdated

--with-dependencies (-w) also updates that package's own dependencies; use --with-all-dependencies (-W) when shared dependencies must move too. If the fixed version needs a newer major release or a newer PHP, composer prohibits vendor/package 2.0 explains what is blocking it. Commit the updated composer.lock, run your tests, and deploy with composer install from that lock file rather than running update on the server.

Ignoring an advisory, with a reason

Sometimes an advisory does not apply, for example when it affects a feature you never call. From Composer 2.10 you can record that decision in composer.json with a reason, scoped to blocking, auditing or both:

{
    "config": {
        "policy": {
            "advisories": {
                "ignore-id": {
                    "GHSA-xxxx-xxxx-xxxx": "Not affected: feature X is disabled. Review by 2027-01-31."
                }
            },
            "abandoned": {
                "audit": "report"
            }
        }
    }
}

Advisory IDs can be CVE, GHSA or Packagist (PKSA) identifiers. Keep each ignore narrow, write down why, and review the list regularly; an ignore list that only grows is a backlog of unpatched risk. The legacy config.audit keys still work as a fallback, but only when the matching policy section is absent, so migrate them all at once rather than mixing the two.

The cooldown policy: a trade-off

Composer 2.11 can withhold newly published versions for a period you choose, for example "policy": {"cooldown": {"period": "7 days"}}. The documentation's reasoning is that many malicious versions are found and removed within hours or days of publication. The cost is that a genuine security fix is also held back for that period unless the package is listed under the cooldown's ignore key or you pass --no-cooldown for one run. If you enable it, decide in advance how you will apply urgent fixes.

Make it automatic

  • In CI: add a step that runs composer audit --locked. With Composer 2.10 or later it exits with 1 when anything fails the audit, so the pipeline stops. Scripts written for older versions that tested specific exit codes should be reviewed.
  • On a schedule: new advisories are published for code you already deployed. A weekly scheduled CI job, or a cron job on a build server, running the audit against the current lock file catches them between releases.
  • On servers: run composer audit --locked in each deployed project as part of your monthly patch check, using the same PHP version the site uses.

Pitfalls

  • Running an old distribution-packaged Composer and assuming it blocks vulnerable versions.
  • Running composer update directly on production, which changes code nobody tested.
  • Ignoring advisories without a reason or a review date.
  • Treating a clean audit as a clean bill of health: it only knows published advisories for Composer packages.

What you should do

  1. Check composer --version on developer machines, CI and servers; move to a current 2.x release where you can.
  2. Run composer audit now on every project and fix or document each finding.
  3. Add composer audit --locked to CI and to a weekly scheduled job.
  4. Record any ignored advisory under config.policy with a reason and a review date.
  5. Keep patching PHP and the operating system separately; Composer does not cover them.

Sources

SKYLINE Engineering

@skyline

The engineering team at SKYLINE Industrial Solutions. We publish field-tested guides drawn from real KSA and GCC deployments.

See author profile
SKYLINE engineering services

Need this implemented for you?

Reading is free — building it right takes a team. SKYLINE engineers ship Cybersecurity for Aramco vendors, banks, hospitals and government agencies across Saudi Arabia. Talk to us before you start.

Aramco Approved Contractor ISO 9001 · ISO 27001 SAMA CSF aligned NCA ECC ready 247+ KSA clients

Comments

0 total · 0 threads
Be the first to leave a comment.