Laravel Badge

Guide

Using Composer 2.10 to Help Your Users Stay Secure

By Peter Fox

Composer 2.10 makes security visible in ways it never was before. Malware is now filtered before it can be installed. Security advisories block composer update by default. Users who haven't upgraded are running with tools that silently ignore known vulnerabilities.

As a package developer, this shifts something: the ecosystem is getting better at surfacing problems, but only if the information exists to surface. That starts with you.


What Composer 2.10 changes

The headline feature is native malware filtering. Composer now integrates Aikido's threat intelligence database directly into the dependency resolution process. If a package version has been flagged as malicious, it will not be installed — even if it's already in a lockfile and you're running composer install on an existing project. A malicious release that slipped into a lockfile won't silently pull through on CI or in production.

Alongside malware filtering, 2.10 introduces a unified config.policy configuration that replaces the old config.audit settings. Three built-in policies are now active by default:

  • Malware — blocked during both composer update and composer install
  • Security advisories — blocked during composer update, permitted during composer install
  • Abandoned packages — audit-only warnings without blocking

The key word in the second bullet is update. If a dependency in an application has a known CVE, running composer update is now blocked until that advisory is resolved or explicitly acknowledged. Your users who have never seen Composer refuse to update will start seeing it.


How security advisories reach Composer

Composer's audit feature pulls from two sources: the FriendsOfPHP Security Advisories database and the GitHub Advisory Database. When a CVE is filed against a PHP package and added to either database, Composer knows about it.

When a user runs composer audit, Composer compares their installed packages against those databases and reports matches. With 2.10's unified policy, security advisories also block composer update by default — they're no longer just informational.

This is the mechanism. What it needs to function is accurate advisory data and accurate version constraints. Both are things you control as a package developer.

Why bumping minimum versions matters

When you release a security fix, the most effective thing you can do for downstream users is make the unsafe version range explicit in your dependencies.

Consider this: your package depends on vendor/widget, which had a vulnerability in 1.2.0 through 1.2.2 that was fixed in 1.2.3. If your composer.json still declares "vendor/widget": "^1.2.0", you're endorsing the vulnerable range for every project that installs your package. Composer will happily resolve to 1.2.0 if that's what the lock file contains, and a composer update on unrelated packages won't change that.

Bumping to ^1.2.3 changes this:

{
    "require": {
        "vendor/widget": "^1.2.3"
    }
}

Now any project installing your package is forced to resolve vendor/widget to at least 1.2.3. The safe version becomes a transitive requirement, not an assumption.

This matters even more for your own packages. If you've patched a vulnerability in your-vendor/your-package and released 1.5.1, users pinned to 1.5.0 won't automatically be told by Composer that anything is wrong — unless an advisory exists for the affected range, or a package they depend on explicitly requires >=1.5.1. Taking both steps closes the gap.


Filing advisories for your own packages

If a vulnerability is found in your own package, the advisory needs to exist before Composer can surface it to anyone.

The canonical source for PHP package advisories is the FriendsOfPHP Security Advisories repository. Submitting is a pull request with a YAML file describing the affected versions:

composer-package: vendor/your-package
cvss: 8.1
branches:
    "1.x":
        tag: true
        versions:
            - ">=1.0.0"
            - "<1.5.1"

The GitHub Advisory Database is also worth filing through its submission form. Packagist imports from both sources, so publishing to either means Composer 2.10's security policy will pick it up.

Once an advisory is published, every user running composer audit against an affected version will see it. With 2.10's blocking policy, users running composer update will have their update stopped until they move to a safe version or explicitly configure the advisory as non-blocking. That's a meaningful change from the old world where advisories were something you had to go looking for.

Adding composer audit to your CI

Running composer audit in your own package's CI pipeline keeps your dependencies clean and models the practice for contributors.

# GitHub Actions
- name: Audit dependencies
  run: composer audit

composer audit exits non-zero if any advisories are found, which fails the workflow. The default output lists each affected package, the advisory title, and a link to the full advisory details.

If you're intentionally staying on a version while waiting for a fix upstream, you can generate audit output without failing the build:

composer audit --format=json > audit-report.json

Or use the config.policy settings introduced in 2.10 to configure specific advisories as audit rather than block, giving you a warning without a hard failure while you track the fix.


The practical checklist

For each package you maintain:

  1. Run composer audit in your package root now. If anything comes back, bump your require constraints to the patched versions and release a new version of your package.

  2. Set minimum patch version constraints, not just major-range ones. Use ^1.5.1 rather than ^1.0 when you know a patch release closed a real vulnerability. This propagates the safe minimum through the entire dependency tree of every application that installs your package.

  3. File an advisory if you've fixed a vulnerability in your own package. Don't assume users will find the changelog entry. A published advisory makes Composer do the finding for them.

  4. Add composer audit to your CI. A package with unacknowledged vulnerable dependencies signals that something isn't being watched.

  5. Set a minimum PHP version that rules out end-of-life releases. PHP EOL branches don't receive security patches. Requiring >=8.2 in your composer.json applies gentle pressure on users who haven't upgraded and signals which environments you're actually testing against.


Composer 2.10 raises the floor

The shift from "auditing as an opt-in check" to "security advisories blocking updates by default" is significant. Users who have never thought about composer audit will still have their composer update blocked when they hit an advisory.

That only helps if the advisory data is accurate and your package's version constraints reflect reality. The ecosystem is tightening around this — Packagist is adding malware detection, stable version immutability, and transparency logging. Composer is making security enforcement a default rather than something you have to configure.

The remaining gap is accurate dependency metadata. That's the part package developers fill.

For more on securing the accounts and infrastructure behind your packages, see Securing Your Packagist Account and Securing Your Laravel Packages with Laravel Moat.