A software dependency is code your project uses but did not write. It might be a library, a package, a build plugin or a tool in the pipeline. A dependency becomes a security risk in three ways. It can contain a flaw that attackers can reach. Someone can tamper with one of its releases. Or it arrives transitively, pulled in by another dependency, so nobody on the team knows it is there.
Most of the defensive work comes down to one question you should be able to answer within minutes: are we running it, which version, and where?
Four kinds of dependency
Software dependencies do not all reach an application the same way, and each route carries a different risk.
| Kind | Examples | How it gets in | Typical risk |
|---|---|---|---|
| Direct | A library listed in package.json, Cargo.toml, pom.xml or requirements.txt | Someone on the team chose it | A version with a known flaw stays pinned |
| Transitive | The packages your direct libraries need | The package manager resolves them automatically | Nobody reviews them, and a new version can arrive without any change to your own code |
| Build-time and development | Compilers, bundlers, test runners, package install scripts | Run on developer machines and in CI | They run with access to tokens and secrets and can change the shipped artifact |
| System | Libraries in the operating system or container base image | Installed with the distribution or image | They are patched on the distribution’s schedule, not yours |
Here is how quickly the transitive layer grows. Running python -m pip show requests on requests 2.34.2 prints Requires: certifi, charset_normalizer, idna, urllib3. One declared line installs four more packages, and each of them is code your program loads. The pip show command reports these in its Requires and Required-by fields.
Two incidents that show the routes
| Date | What happened | Route | Source |
|---|---|---|---|
| March 29, 2024 | Malicious code was found in XZ Utils 5.6.0 and 5.6.1 (CVE-2024-3094, CVSS 10.0). It sat in the upstream release tarballs. During the build, liblzma extracted a prebuilt object file from a disguised test file. | System library, inserted at build time | NVD; CISA |
| September 23, 2025 | CISA reported that a self-replicating worm known as “Shai-Hulud” had compromised more than 500 npm packages. It harvested GitHub tokens and cloud API keys, then used the developer’s npm access to publish infected versions of other packages. | Transitive packages and developer credentials | CISA |
For XZ Utils, CISA advised downgrading to an uncompromised version such as 5.4.6 Stable. In the npm case, CISA told organizations to check package-lock.json or yarn.lock for affected packages, “including those nested in dependency trees”. It also advised pinning to releases published before September 16, 2025, rotating developer credentials and requiring phishing-resistant multifactor authentication. Neither case was an ordinary bug. In both, the release itself was the attack.
How to list every dependency
You can only answer an advisory if you can see the full tree. Every major ecosystem has a built-in command for this, and most can also tell you why a particular package is present.
| Ecosystem | Full tree | Why is this package here? |
|---|---|---|
| npm | npm ls --all | npm explain <package> |
| Python (pip) | python -m pip show <package> (Requires) | Same command, Required-by field |
| Go | go mod graph | go mod why <package> |
| Rust | cargo tree | cargo tree -i <package> |
| Maven | mvn dependency:tree | Add -Dincludes=groupId:artifactId |
| Gradle | ./gradlew dependencies | ./gradlew dependencyInsight --dependency <name> --configuration <config> |
The npm documentation notes that npm ls shows the logical dependency tree, not the physical layout of node_modules. Cargo marks repeated entries with (*). For a record you can keep and share, a software bill of materials captures the same inventory in a standard format.
A vulnerability and a compromised release are different
A dependency can contain an ordinary defect that attackers exploit. It can also be altered on purpose, through a compromised maintainer account, build environment or distribution channel.
The response is different for each. Updating away from a known vulnerable version fixes the first problem. The second needs proof that a downloaded artifact came from the expected source. The SLSA specification describes increasing levels of supply-chain protection, including source and build information. Our guide to SLSA build provenance explains what each level shows. Provenance can establish how an artifact was produced, but it does not prove the source code is free of flaws. Reproducible builds add an independent check that a binary matches its source.
Names and versions need verification
A package with a similar name can lead you to install the wrong project. A familiar name can also change hands to new maintainers or move to a different distribution arrangement.
Install from the route the project documents, and verify the package’s identity. Where the ecosystem supports it, keep dependency resolution reproducible so that a later build does not quietly pick a different set of versions. A lockfile records which versions were resolved, which is exactly what CISA asked teams to search in 2025. It does not make those packages safe. Semantic versioning ranges decide how far an unlocked dependency is allowed to move.
Reachability affects the impact
An advisory might describe a vulnerable function your application never calls, or a configuration you do not use. On the other hand, a small dependency can sit on a highly exposed path.
Before you decide how to respond, check the actual version, the feature involved and how input reaches it. A scanner finding is where the investigation starts, not a full account of the impact in production. Do not dismiss a finding just because the library is indirect. Transitive code can still process untrusted input, and build-time code can run with privileged access.
Updates need a verification path
Keeping dependencies current reduces exposure to known defects, but any update can also change behavior. Pair timely review with tests that cover the affected functionality. For a critical component, know how to roll back the application, and check whether data changes would complicate a rollback. Our AI coding-assistant comparison explains why a reported test pass should be checked against the actual change. The same applies when automation proposes dependency updates. Once a component reaches end of life, updating it is no longer an option, and replacing it is the only remedy.
Reduce unnecessary exposure
Remove dependencies that no longer do a job. Limit what build and runtime environments can reach, and do not give every tool broad credentials. The npm worm spread by scanning the environment for credentials it could reuse. These steps do not remove supply-chain risk, but they shrink the amount of software and authority involved when something goes wrong.
The EU reporting clock
Under the Cyber Resilience Act, manufacturers of products with digital elements have had to report actively exploited vulnerabilities and severe incidents since September 11, 2026. They must send an early warning within 24 hours, a full notification within 72 hours, and a final report no later than 14 days after a corrective measure is available. The Act’s main obligations apply from December 11, 2027, according to the European Commission. With deadlines counted in hours, a dependency inventory that is already up to date matters.





