Server
Vulnerable dependencies: composer, npm and supply chain security
Even clean code becomes vulnerable through an outdated library. composer audit, npm audit, lock files, Dependabot, abandoned packages and SBOM — a practical guide.
· 10 min read

In a modern web application, the code you wrote yourself is a small part of the whole. A Laravel project pulls hundreds of packages into vendor, and a simple React frontend pulls even more into node_modules. Each of them was written by other people, and any of them can turn out to have a vulnerability.
So security does not end with checking your own code. In this post we show how to find vulnerabilities in your dependencies, how to update them and how to automate the process.
What the problem is
There are two kinds of risk:
- Known vulnerabilities. A vulnerability is found in a library, gets a CVE, and a fixed version is released. If you stay on the old version, the vulnerability is open and its details are public. OWASP Top 10 (2021) has a dedicated category for this: A06 — Vulnerable and Outdated Components.
- Supply chain attacks. The package itself is malicious: its name imitates a popular package (typosquatting), the author's account is taken over and malicious code is added to a new version, or an install script runs code on the server or the developer's machine.
The first risk is routine and easy to manage. The second is rarer but harder to detect.
Code you didn't write is still your code. For the expertise,
vendorandnode_modulesare part of the application.
Why it matters for the expertise
Expertise scanners identify the library versions an application uses in several ways:
- JavaScript libraries on the frontend (for example, old versions of jQuery or Bootstrap) are identified by file name and the comments inside.
- An exposed
composer.lock,package.jsonorpackage-lock.jsonhands over every package and version in a single list. - HTTP headers and error pages can reveal the framework version.
Once a version is identified, the matching known vulnerabilities go into the report as findings. So before submission you need to check your dependencies and make sure lock files are not exposed.
Check:
for f in composer.json composer.lock package.json package-lock.json yarn.lock .env; do
printf '%s ' "$f"; curl -s -o /dev/null -w '%{http_code}\n' "https://site.uz/$f"
done
Every response should be 404 or 403. In Laravel, if the web root is the public/ folder, these files naturally stay outside it. If the root is misconfigured, they will be exposed.
How to check it yourself
PHP: composer audit
Since Composer 2.4 there is a built-in audit command. It compares the packages in composer.lock against a database of known vulnerabilities:
composer audit
composer audit --no-dev # production packages only
composer audit --format=json # for CI
List outdated packages:
composer outdated --direct
--direct shows only the packages you declared in composer.json yourself — updating them often updates the nested dependencies too.
JavaScript: npm audit
npm audit
npm audit --omit=dev # production dependencies only
npm audit --audit-level=high # high and critical only
npm outdated
To find out where a vulnerable package comes from:
npm ls <package-name>
npm audit fix updates to compatible versions. npm audit fix --force may also bump major versions, which can break the application — only use it together with tests.
It is also worth verifying package signatures from the npm registry:
npm audit signatures
Universal: OSV-Scanner
For projects that mix several ecosystems, OSV-Scanner checks all lock files at once against the open OSV database:
osv-scanner scan source -r .
Flutter and Dart
The same principle applies to mobile apps:
flutter pub outdated
Third-party SDKs in a mobile app (analytics, ads, payments) are dependencies too and may be checked during the expertise.
Lock files: reproducible installs
composer.lock and package-lock.json record exactly which versions get installed. Without them, each install may pull in different versions — including new, not-yet-vetted and potentially malicious ones.
Rules:
- Lock files are committed to the repository.
- In production and CI, install from the lock file without updating it:
composer install --no-dev --prefer-dist --optimize-autoloader
npm ci --omit=dev
npm install may modify the lock file, while npm ci follows it strictly and fails if it does not match package.json.
- Review lock file changes in code review. Hundreds of changed lock lines for a small change are a reason to ask questions.
Automation: Dependabot and Renovate
A manual check is useful once, but vulnerabilities are published every day. Connect a tool that proposes updates automatically.
For Dependabot on GitHub, .github/dependabot.yml:
version: 2
updates:
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
groups:
minor-and-patch:
update-types: ["minor", "patch"]
Dependabot opens pull requests with updates every week, and security alerts arrive separately. Renovate is a similar tool that also works on GitLab and other platforms and allows fine-grained grouping and auto-merge rules.
The key is not to let PRs pile up. Build a habit of reviewing and merging them once a week; if the tests are green, don't delay patch updates.
Add a check to CI as well, so code with a vulnerable package never reaches production:
# GitHub Actions steps
- run: composer install --no-interaction --prefer-dist
- run: composer audit --no-dev
- run: npm ci
- run: npm audit --omit=dev --audit-level=high
Abandoned packages
No vulnerability has been published, but the author has stopped maintaining the package — that is a risk too. When the next vulnerability is found, no fix will come.
Composer warns about abandoned packages during install and update. In Composer 2.7 and later, the audit checks this as well, and you can set the policy in composer.json:
{
"config": {
"audit": {
"abandoned": "fail"
}
}
}
In npm, deprecated packages show up during installation as npm warn deprecated — don't ignore these warnings. For every abandoned package, find a replacement or remove it.
Protecting the supply chain
- Check the package name. Before adding a new package, look at its official name, download count, repository and recent activity. A one-letter difference is a sign of typosquatting.
- Limit install scripts. npm packages can run scripts such as
postinstall. If you don't need them in CI, usenpm ci --ignore-scripts(but note that some packages don't work without their scripts). - Fewer dependencies, less risk. Don't add a package for a one-line task.
- Add SRI to scripts loaded from a CDN. Then if the file on the CDN is tampered with, the browser won't execute it:
<script src="https://cdn.example.com/lib.min.js"
integrity="sha384-..."
crossorigin="anonymous"></script>
- Don't build on the production server. Build the frontend in CI and deploy the finished artifact — the server needs neither
node_modulesnor build tools.
SBOM: know what you are made of
An SBOM (Software Bill of Materials) is a list of every component in the application and its version, in a standard format (CycloneDX or SPDX). When a new vulnerability is published, an SBOM lets you answer "do we have this package, and which version?" in seconds.
# npm (npm 10+)
npm sbom --sbom-format cyclonedx > sbom-npm.json
# Composer (via the CycloneDX plugin)
composer require --dev cyclonedx/cyclonedx-php-composer
composer CycloneDX:make-sbom --output-file=sbom-php.json
Store the SBOM with every release. It helps both in the expertise and with customers: you can show exactly what the application consists of.
Short checklist
composer audit --no-devandnpm audit --omit=devshow no serious vulnerabilities.composer.lockandpackage-lock.jsonare in the repository; production usescomposer installandnpm ci.- Lock files,
package.jsonand.envcannot be opened from a browser. - The frontend has no old jQuery, Bootstrap or similar libraries.
- Dependabot or Renovate is connected and PRs are reviewed regularly.
- CI stops the build on a vulnerable dependency.
- Abandoned and deprecated packages are replaced.
- CDN scripts have SRI; nothing is built on the production server.
- An SBOM is generated for every release.
Summary
Managing dependencies is not a one-off job but a process. Running composer audit and npm audit once and closing what they find is a good start for the expertise. But in the long run, lock files, automatic updates and CI checks keep the application in a safe state every day. We covered keys and passwords stored in code in our post on managing secrets and keys.
Want to know what state your project's dependencies and server configuration are in? Request a security scan — the Claude AI agent and our specialist will review your codebase and configuration, and we will help close the holes we find and prepare for the expertise.


