Milind Daraniya

Your Composer and npm Dependencies Can Become Your Biggest Security Risk

Published August 20th, 2026 19 min read

As developers, we trust our dependencies a lot.

In a Laravel project, I can run:

composer install

and suddenly my project has dozens or even hundreds of packages.

On the frontend, it can be even more.

npm install

and our application gets a large dependency tree.

Most of the time this is exactly what we want.

We don't want to build a logging library, HTTP client, date library, authentication package or frontend utility from zero.

But there is one thing we sometimes forget:

Our application is not only our code.

It is also the code we depend on.

This is why software supply-chain security is becoming a much bigger topic in modern development. OWASP's 2025 Top 10 has expanded this area into A03: Software Supply Chain Failures, covering not only vulnerable libraries but also compromised dependencies, build systems, CI/CD, package repositories and the broader software delivery process.

We usually think only about our own code

When I review an application, the first thing I normally think about is:

Is my PHP code secure?

Is my Laravel code secure?

Are my SQL queries safe?

Are my APIs protected?

These are important questions.

But there is another question:

What exactly am I installing into my application?

Suppose I have:

My Laravel Application
        |
        ├── Laravel
        ├── Payment Package
        ├── Excel Package
        ├── PDF Package
        ├── HTTP Client
        ├── Logging Package
        └── 50+ Other Dependencies

Even if my own code is perfect, a compromised package can become a problem.

That is the part many developers don't think about.

The dependency tree is much bigger than it looks

When I run:

composer require some/package

I may think:

"I installed one package."

Technically, that is often not the complete story.

That package can depend on other packages.

Those packages can depend on more packages.

So we have:

My Application
    ↓
Package A
    ↓
Package B
    ↓
Package C
    ↓
Package D

Package D may not be something I have ever directly chosen.

But it is still part of my application.

OWASP specifically points out that developers need to track both direct and transitive dependencies because an untracked dependency can remain outside normal security monitoring.

This is why:

"I don't use that package directly"

does not automatically mean:

"That package cannot affect my application."

Composer is not just a convenience tool

As a Laravel developer, Composer is part of my daily life.

We use it for:

  • Laravel
  • Symfony components
  • Guzzle
  • Monolog
  • PHPUnit
  • SDKs
  • Payment gateways
  • Excel libraries
  • PDF libraries
  • Authentication packages
  • Internal packages

Composer makes development much faster.

But every package we install increases the software supply chain we need to trust.

That doesn't mean we should stop using open-source packages.

That would be unrealistic.

It means we should become more aware of what we are actually installing.

npm makes the problem even more interesting

Frontend dependency trees can become very large.

One package can bring multiple dependencies.

And those dependencies can bring more dependencies.

For a Laravel application using React, Vue or another frontend framework, we may have both:

PHP / Composer dependencies

and:

JavaScript / npm dependencies

So our application can have two completely different dependency ecosystems.

This means dependency security should not be treated as only a PHP problem.

It is an application-wide problem.

A vulnerable package is not the only problem

This is something I think is very important.

When people hear "dependency security", they usually think:

"There is a CVE in an old package."

That is only one part.

Modern software supply-chain risks can also include:

  • A malicious package
  • A compromised maintainer account
  • A dependency being taken over
  • Dependency confusion
  • Typosquatting
  • Compromised build systems
  • Malicious CI/CD changes
  • Compromised plugins
  • Untrusted package sources
  • Tampered artifacts
  • Stolen deployment credentials

OWASP's software supply-chain guidance specifically includes source-code threats, build-environment threats, dependency threats and deployment/runtime threats.

So the problem is much larger than:

"Run an update once a month."

Imagine a package suddenly becomes malicious

Let's imagine I install:

composer require example/package

Everything works.

After six months, somebody gains access to the package's publishing process.

A new version is published.

My CI pipeline automatically installs the newest compatible version.

Now the malicious code may enter my build without me changing my application code.

This is the scary part.

From the Git commit history, I may see:

No application code changed.

But the software that gets deployed has changed.

That is why dependency updates should be treated as software changes.

"Latest version" is not always the safest strategy

I have seen developers do:

composer update

and:

npm update

without checking what is going to change.

Sometimes it works.

Sometimes it creates unexpected breaking changes.

From a security point of view, blindly updating everything is also not a great strategy.

We should know:

  • What changed
  • Which package changed
  • Why it changed
  • Whether the package is maintained
  • Whether the new version fixes a vulnerability
  • Whether it introduces breaking changes
  • Whether the build is reproducible

OWASP recommends deliberate dependency management, monitoring for vulnerable or unmaintained components and applying updates according to risk rather than treating patching as a blind periodic task.

What about composer.lock?

This file is extremely important.

For an application, I prefer having the exact resolved dependency versions recorded in:

composer.lock

This gives the team a reproducible dependency state.

The same idea applies to:

package-lock.json

or the equivalent lock file used by the frontend package manager.

The difference between:

"Install whatever version is currently available"

and:

"Install the exact versions we have tested"

is very important for production systems.

But lock files are not a security guarantee by themselves.

A locked malicious package is still a malicious package.

The lock file gives us reproducibility.

It does not automatically give us trust.

CI/CD is part of the supply chain too

This is an area developers sometimes forget.

We protect:

Production server

but don't pay enough attention to:

Git repository
CI/CD
Build server
Package registry
Deployment credentials
Artifact storage

But the CI/CD pipeline is effectively a path into production.

For example:

GitHub
   ↓
CI/CD
   ↓
composer install
   ↓
npm install
   ↓
Build
   ↓
Deploy

If the pipeline is compromised, an attacker may not need to attack the production server directly.

They can attack the process that builds the application.

OWASP explicitly notes that CI/CD environments can have weaker security than the systems they build and deploy, making pipeline protection an important part of supply-chain security.

Never keep production secrets casually available in CI

This is another lesson I take seriously.

A deployment pipeline may have access to:

AWS credentials
Database passwords
Application secrets
SSH keys
API keys
Payment gateway credentials

If the pipeline or repository account is compromised, these secrets can become more dangerous than the vulnerable dependency itself.

So CI/CD needs:

  • Least privilege
  • Protected environments
  • Strong authentication
  • Secret management
  • Restricted deployment permissions
  • Branch protection
  • Audit logs

The goal should be:

Compromising one part of the development environment should not automatically give access to everything.

Should we stop using open source?

Absolutely not.

Open source is one of the biggest reasons modern software development is so fast.

Laravel itself is part of the open-source ecosystem.

Composer packages allow us to build products much faster.

Frontend packages allow small teams to build applications that previously required much larger teams.

The answer is not:

"Don't use dependencies."

The answer is:

Know your dependencies.

I would start with a dependency inventory

For every production application, I want to know:

What packages are installed?

Which versions are installed?

Which packages are direct dependencies?

Which are transitive dependencies?

Which are no longer maintained?

Which have known vulnerabilities?

Which package registry do they come from?

Who can change the dependency?

How is the dependency introduced into production?

This is basically the idea behind a Software Bill of Materials, or SBOM.

OWASP recommends maintaining visibility into the software components and dependencies used by an application and supports automated SBOM and dependency monitoring approaches.

Dependency scanning should become normal

I don't think security scanning should happen only before a big release.

It should become part of CI.

For example:

Pull Request
     ↓
Tests
     ↓
Dependency Security Check
     ↓
Build
     ↓
Deploy

If a serious dependency vulnerability appears, the team should know about it quickly.

This is much better than discovering six months later that an important package has been vulnerable for a long time.

OWASP recommends continuous monitoring of dependency versions and known vulnerabilities and mentions tools such as Dependency-Track and Dependency-Check as examples of automated approaches.

Remove dependencies you don't need

This sounds simple, but it is very useful.

Every dependency has:

  • Maintenance cost
  • Upgrade cost
  • Security risk
  • Compatibility risk
  • Operational risk

Sometimes we install a package for one small feature.

Six months later the feature is removed.

But the package remains.

Now we have a dependency that provides no business value.

I prefer periodically asking:

"Why is this package still here?"

If there is no good answer, remove it.

Reducing dependencies also reduces the attack surface. OWASP explicitly recommends removing unused dependencies and unnecessary components.

Don't copy random code into your project

There is another supply-chain problem that is easy to miss.

Developers sometimes copy code from:

  • GitHub
  • Stack Overflow
  • Blogs
  • Gists
  • Forums
  • Random repositories

Then that code becomes part of the application.

Now it may not appear in:

composer.json

or:

package.json

So normal dependency tracking may not even know it exists.

OWASP's open-source software risk guidance specifically calls out copied or vendored code as one reason dependencies can remain untracked.

I am not saying:

"Never copy code."

I am saying:

Know what you are copying.

Don't trust package names blindly

Typosquatting is another simple example.

Suppose the package we want is:

awesome-library

An attacker may publish:

awesome-libray

One small typing mistake.

If someone installs it without checking, the malicious package can enter the project.

This is why package source and package identity matter.

OWASP recommends obtaining components from trusted sources and, where appropriate, verifying package provenance and signatures.

The development laptop is also part of the supply chain

We sometimes think:

"My production server is secure."

But what about the developer laptop?

What about:

  • IDE extensions
  • Git credentials
  • SSH keys
  • Composer credentials
  • npm credentials
  • Cloud CLI credentials
  • Browser sessions
  • Local environment variables

OWASP's guidance explicitly includes developer workstations, IDEs, repositories, CI/CD systems and artifact repositories within the software supply-chain security picture.

This means security starts before the code reaches Git.

My practical approach for a Laravel project

For a Laravel application, I would keep the process simple.

First:

composer validate

Then inspect the dependency tree and outdated packages.

Regularly check:

composer audit

For JavaScript:

npm audit

But I would not blindly fix everything returned by these commands.

I would review:

  • Severity
  • Affected package
  • Whether the package is actually used
  • Whether the vulnerable code path is relevant
  • Available fixed versions
  • Compatibility impact
  • Whether the dependency can be removed

Security tooling gives us information.

Developers still have to make the decision.

One more thing: deployment should be reproducible

I don't like the idea of production being built from:

whatever packages happen to be available today

I prefer:

Known source
+
Known dependency versions
+
Known build process
+
Known artifact
=
Production deployment

That makes debugging easier.

It also makes security incidents easier to investigate.

If something changes unexpectedly, we have a better chance of knowing where it came from.

The biggest lesson

As developers, we like to say:

"I wrote this application."

But technically, modern applications are assembled from thousands of pieces.

Our code.

Open-source libraries.

Frameworks.

Operating systems.

Build tools.

CI/CD systems.

Cloud services.

Package registries.

Third-party APIs.

That is our real software supply chain.

So application security cannot stop at our controller code.

We have to secure the path from:

Developer → Git → Dependencies → Build → Artifact → Deployment → Production

A vulnerability in our own code is a problem.

A compromised dependency can be a much bigger problem because we may trust it automatically.

That is why I think dependency management should not be treated as housekeeping.

It is part of application security.

And for anyone building Laravel, PHP, React or SaaS products, this is becoming something we cannot ignore anymore.