How to Find Vulnerable Dependencies with npm audit, pip-audit and composer audit
Most of the code in a modern project is not code you wrote. A small PHP app pulls in a dozen Composer packages, a React front end can install hundreds of npm packages, and even a simple Python script often depends on libraries that depend on other libraries. Every one of those packages can contain a known security vulnerability, and attackers actively look for sites running old, vulnerable versions.
The good news is that npm, Python, and PHP all have free tools that check your dependencies against public vulnerability databases in seconds. In this guide you will learn how to use npm audit, pip-audit, and composer audit, how to read their output, how to fix what they find, and how to combine them into one script you can run before every deployment.
All commands and outputs below come from real test runs on a Linux machine with Node.js 20, npm 11, Python 3.13, pip-audit 2.10, PHP 8.4, and Composer 2.11. Advisory IDs and version numbers will change over time as new vulnerabilities are published, so your results may look different.
Why Dependency Audits Matter
When a vulnerability is found in a popular package, it gets a public advisory, usually with a CVE or GHSA identifier, listing the affected versions and the version that fixes it. From that moment, the problem is public knowledge. If your project still uses an affected version, you are exposed, even if your own code is perfect.
Audit tools compare the exact versions in your project against those advisory databases. They do not scan your code for bugs. They answer one simple question: "Am I using a package version that is publicly known to be vulnerable?" That makes them fast, cheap, and a great first line of defense.
JavaScript: npm audit
npm audit is built into npm, so there is nothing to install. It reads your package-lock.json and checks every installed package, including the dependencies of your dependencies. To see it in action, we installed an old version of lodash in an empty project:
npm init -y
npm install [email protected]
npm audit
The output (shortened here) looked like this:
# npm audit report
lodash <=4.17.23
Severity: high
Command Injection in lodash - https://github.com/advisories/GHSA-35jh-r3h4-6jhm
Prototype Pollution in lodash - https://github.com/advisories/GHSA-p6mc-m468-83gw
...
fix available via `npm audit fix`
node_modules/lodash
1 high severity vulnerability
Each line links to a GitHub advisory, so you can read what the problem is and whether it affects how you use the package. To fix it, run:
npm audit fix
In our test this upgraded lodash to 4.18.1, and running npm audit again reported found 0 vulnerabilities. Because package.json allowed ^4.17.15, npm could update within the same major version safely.
Be careful with npm audit fix --force. It is allowed to install new major versions, which can include breaking changes. Only use it when you are ready to test your app properly afterwards.
Only fail on serious problems
The --audit-level option controls which severity makes the command fail. With the vulnerable lodash installed, npm audit --audit-level=critical exited with code 0 (the issue was "high", not "critical"), while npm audit --audit-level=high exited with code 1. That exit code is what a deployment script or CI pipeline uses to decide whether to stop.
Python: pip-audit
Python does not ship an audit command, but pip-audit, maintained under the Python Packaging Authority (PyPA), does the job. Install it in a virtual environment:
python3 -m venv venv
source venv/bin/activate
pip install pip-audit
You can audit your current environment by running pip-audit on its own, or audit a requirements file without installing it. We created a requirements.txt with one old pinned package:
requests==2.25.0
Then ran:
pip-audit -r requirements.txt
The result:
Found 24 known vulnerabilities in 3 packages
Name Version ID Fix Versions
-------- ------- --------------- ------------
requests 2.25.0 PYSEC-2023-74 2.31.0
requests 2.25.0 PYSEC-2026-1873 2.32.0
...
idna 2.10 PYSEC-2024-60 3.7
...
urllib3 1.26.20 PYSEC-2026-1999 2.5.0
...
Notice something important: we only listed one package, but pip-audit found problems in three. The old version of requests pulled in old versions of idna and urllib3, and those had their own advisories. This is exactly why you should audit the full dependency tree, not just the packages you remember adding.
The "Fix Versions" column tells you the minimum safe version for each advisory. After changing the file to requests==2.33.0, pip-audit reported No known vulnerabilities found and exited with code 0. (With the vulnerable file it exited with code 1.)
PHP: composer audit
Composer has included an audit command for several years, and recent versions go further. When we tried to install an old, vulnerable version of guzzlehttp/psr7 with Composer 2.11, Composer refused:
php composer.phar require guzzlehttp/psr7:2.4.0
Problem 1
- Root composer.json requires guzzlehttp/psr7 2.4.0 (exact version match: 2.4.0 or 2.4.0.0),
found guzzlehttp/psr7[2.4.0] but these were not loaded, because they are affected
by security advisories ...
That is Composer's policy.advisories.block setting, which is on by default and stops you from adding known-vulnerable versions during require or update. It does not help with packages that were already in your lock file before an advisory was published, which is the common case on older projects. For that you need composer audit.
For the demo we turned the block off in a throwaway project (do not do this in a real one), installed the old version, and ran:
composer audit
Composer printed one table per advisory. Here is one of them, trimmed to the most useful rows:
Found 5 security vulnerability advisories affecting 1 package:
+-------------------+------------------------------------------------------------------+
| Package | guzzlehttp/psr7 |
| Severity | medium |
| Advisory ID | PKSA-hn62-zkx4-1y5q |
| CVE | CVE-2023-29197 |
| Title | Improper header validation |
| Affected versions | >=2,<2.4.5|<1.9.1 |
+-------------------+------------------------------------------------------------------+
...
For a short version, use composer audit --format=summary. To fix the issue we required a safe version:
composer require "guzzlehttp/psr7:^2.12.3"
After that, composer audit reported No security vulnerability advisories found.
Audit the lock file, not just vendor/
By default, composer audit checks installed packages. In a fresh checkout or a CI job where you have not run composer install yet, it stops with "No installed packages found." Add --locked to audit composer.lock directly. We hit this exact problem while writing the script below, so it is worth remembering.
One Script for All Three
Many projects mix languages, for example a PHP back end with an npm build step. This small Bash script runs whichever audits apply to the current folder and fails if any of them finds a problem:
#!/usr/bin/env bash # audit-deps.sh - check every dependency file in this folder for known vulnerabilities status=0 if [ -f package-lock.json ]; then echo "== npm audit ==" npm audit --audit-level=high || status=1 fi if [ -f requirements.txt ]; then echo "== pip-audit ==" pip-audit -r requirements.txt || status=1 fi if [ -f composer.lock ]; then echo "== composer audit ==" composer audit --locked --format=summary || status=1 fi if [ "$status" -ne 0 ]; then echo "Vulnerable dependencies found. Please update before you deploy." else echo "All dependency checks passed." fi exit $status
Save it as audit-deps.sh in your project root and make it executable with chmod +x audit-deps.sh. On a project with all three dependency files fully updated, it printed:
== npm audit ==
found 0 vulnerabilities
== pip-audit ==
No known vulnerabilities found
== composer audit ==
No security vulnerability advisories found.
All dependency checks passed.
and exited with code 0. With the old lodash and requests versions back in place, it printed the reports and ended with "Vulnerable dependencies found. Please update before you deploy." and exit code 1. Because it relies only on exit codes, you can call it from a Git hook, a deployment script, or any CI service.
Tips for Making Audits a Habit
- Commit your lock files.
package-lock.jsonandcomposer.lockrecord the exact versions you run. Without them, audits are guesses. For Python, pin versions inrequirements.txt. - Run audits on a schedule, not only when you change code. New advisories are published every day. A project that passed last month can fail today without a single code change.
- Read before you upgrade. An advisory might affect a feature you do not use. That does not mean you should ignore it, but it helps you decide what to fix first. GitHub's new Dependabot reachability analysis automates this kind of triage for some JavaScript and TypeScript repositories.
- Test after every fix. Security updates can still change behavior. Run your tests or at least click through the main features before you deploy.
- Remove what you do not use. The safest dependency is the one you deleted. Unused packages still show up in audits and still ship to production.
Conclusion
Checking your dependencies for known vulnerabilities takes less than a minute, and the tools are free: npm audit for JavaScript, pip-audit for Python, and composer audit for PHP. Run them on your current projects today, fix what they find, and add a script like audit-deps.sh to your deployment routine. It is one of the easiest security wins available, and it protects your users from problems that attackers already know about.