Composer: Audit
When to Use
Use composer audit --locked to check the lock file for packages with security advisories and for abandoned packages. On Composer 2.10 and later it exits 0 when clean and 1 when it finds anything.
Decision
| If you need... | Use... | Why |
|---|---|---|
| To audit what the project will deploy | composer audit --locked |
Reads composer.lock, whatever vendor/ holds |
| A machine-readable result | --format=json |
Also table, plain, summary |
| To accept one advisory (2.10+) | config.policy.advisories.ignore-id with a reason |
The policy block replaces most of config.audit |
| To accept one advisory (before 2.10) | config.audit.ignore with a reason |
Deprecated in 2.10, still read when policy is absent |
| To accept an abandoned package (2.10+) | config.policy.abandoned.ignore with a reason |
Accepts names and patterns |
| To accept an abandoned package (2.9.x) | config.audit.ignore-abandoned with a reason |
Added in 2.9.0; deprecated in 2.10; not available on 2.7–2.8 |
Pattern
{
"config": {
"policy": {
"advisories": {
"ignore-id": {
"GHSA-xxxx-xxxx-xxxx": "Affected path not reachable; the module's REST resource is disabled."
}
},
"abandoned": {
"ignore": {
"vendor/legacy-lib": "Transitive via drupal/foo; removal tracked for next release."
}
}
}
}
}
Reference: Composer doc/06-config.md (policy, audit) and CHANGELOG.md at tag 2.10.3
What Audit Checks and Blocks
- Exit code — from 2.10.0, always 0 or 1. From 2.8.4 to 2.9.x it was 1 for advisories, 2 for abandoned, 3 for both. A CI step that tests
-eq 1on an older Composer misses abandoned packages. - Abandoned packages fail by default —
audit.abandoneddefaults tofailsince 2.7; it wasreportin 2.6. In the 2.10policyblock,policy.abandoned.auditalso defaults tofail. - Insecure versions are blocked during resolution —
audit.block-insecure, added in 2.9 and defaulting totrue, stopsupdate,requireandremovefrom choosing a version with an active advisory. In 2.10 this ispolicy.advisories.block. A blocked version shows up as a resolution failure, not as an audit finding. policyreplacesaudit— the legacyconfig.auditkeys are read only when the matchingpolicysection is absent, all or nothing per policy. Do not split one policy across both blocks.- Implicit audits —
updateandrequireaudit after they finish.--no-auditskips it. Since 2.6.2 a finding in that implicit audit does not change their exit code, so runcomposer auditon its own.
Security Best Practices
- Every accepted entry carries a reason a reviewer can check: why the advisory does not apply, or when the abandoned package leaves. An ignore with no reason is a hidden vulnerability
- Ignore by advisory ID, never by package name. A package-name ignore silences future advisories too
- Do not use
--ignore-severityin CI to make a build pass. It hides every advisory at that level, including new ones - Review accepted entries on every update. Remove each one the update resolves
composer auditis a gate on every build; the code quality checks guide covers wiring it into CI
Common Mistakes
- Auditing
vendor/instead of the lock → Use--locked, so a stale install does not hide a finding - Using
config.audit.ignoreon a 2.10 project that also has apolicy.advisoriesblock → The legacy key is ignored. Move it topolicy.advisories.ignore-id - Accepting an advisory to unblock an update → Fix the resolution first; an ignore is for a finding that does not apply
See Also
- Composer: Update vs Require — the update that resolves most findings
- Related: Code quality checks —
composer auditin CI - Related: OWASP Top 10 in Drupal — vulnerable components
- Recipe:
drupal_dependency_update— an unaccepted finding stops the run - Reference: Composer config: policy