Dependencies and Updates
composer audit checks your lock file against known advisories, and an end-of-life PHP version gets no security fixes at all.
composer audit --lockedA dependency is someone else's code that you ship and run as your own. A vulnerability in it is a vulnerability in your application, and you will not find it by reading your own diff. composer.lock records the exact version of every package you installed, direct and transitive, which is what makes checking them possible.
composer audit
Run composer audit. It compares every version in your project against a database of published advisories and prints what it finds. It exits with a non-zero status when there is anything to report, so it works as a build step that fails. composer audit --locked reads the lock file rather than the installed vendor directory, which is what you want in CI before an install.
Reading an advisory
An advisory names the package, an identifier such as a CVE or GHSA number, the affected version range, and the version that fixes it. Decide two things: whether your version falls in the range, and whether your code reaches the affected feature. Then upgrade with composer update vendor/package --with-dependencies, which moves that package and what it needs and leaves the rest of the lock file alone.
PHP is a dependency too
Each PHP release gets roughly two years of bug fixes, then two more of security fixes only, and nothing after that. PHP 8.1 left security support at the end of December 2025. Check php.net/supported-versions for the dates, because an old runtime is the one dependency no package update can save you from.
Example
When to use it
- Checking a project you have just inherited for known vulnerable packages.
- Adding a dependency check to CI that fails the build instead of printing a warning.
- Deciding whether an advisory for a package you use actually affects your code.
- Planning a PHP upgrade before the version you run stops getting security fixes.
More examples
The commands, in order
Run the first command now and then on a schedule, because the advisory list changes without your code changing. Prefer the targeted update over a bare composer update when you are responding to an advisory: a full update rewrites the whole lock file, and you want a small reviewable diff when the change is urgent. --direct hides the transitive noise you cannot act on directly.
# What is vulnerable right now. Non-zero exit when anything is found.
composer audit
# In CI, before installing anything: read the lock file only.
composer audit --locked
# Machine-readable, for a dashboard or a scripted check.
composer audit --locked --format=json
# Fix one package and whatever it depends on, nothing else.
composer update vendor/mailer --with-dependencies
# What else has moved on, limited to packages you asked for yourself.
composer outdated --direct
# Which PHP version this machine runs, and which extensions are loaded.
php -v
php -mPinning the PHP version you actually support
The require entry stops Composer resolving packages that need a newer PHP than you run. The platform config tells it to resolve as if PHP 8.4.0 were installed, which keeps a developer on 8.5 from locking a package that will not install on the server. Raise both when you upgrade production, and treat the day a version leaves security support as a planned task rather than an emergency.
{
"require": {
"php": "^8.4",
"ext-pdo": "*",
"ext-mbstring": "*"
},
"config": {
"platform": {
"php": "8.4.0"
}
},
"scripts": {
"audit-ci": "composer audit --locked"
}
}A dependency check that fails the build
composer install, as opposed to composer update, installs the pinned versions and nothing else, so what CI tests is what production runs. Letting the audit fail the pipeline is the point: a check whose output nobody reads is not a check. The abandoned report is advisory rather than fatal here, since an abandoned package needs a planned replacement, not a failed deploy.
#!/usr/bin/env bash
set -euo pipefail
# Install exactly what the lock file says. No resolution, no surprises.
composer install --no-interaction --prefer-dist --no-progress
# Fails the script on any advisory, because of set -e above.
composer audit --locked
# A newly abandoned package gets no fixes at all, so it is worth knowing.
composer audit --locked --abandoned=report || true
echo "Dependencies clean"
Discussion