Dependency vulnerabilities and supply chain attacks

Every dependency a project installs is code that runs with the same privileges as the application's own code — a real, common way to get compromised has nothing to do with a bug in code your own team wrote, and everything to do with code a stranger published to a package registry.

Intermediate

4 min read

The core risk: a dependency's code runs with your application's full privileges

// A single line in package.json...
"dependencies": { "some-utility-library": "^2.1.0" }
 
// ...means whatever code that package (and EVERY package IT depends on,
// transitively) contains runs with the SAME privileges as your own
// application code — filesystem access, network access, environment
// variables including secrets, all of it

Installing a dependency doesn't just add its advertised functionality — it grants that package's code (and every package it in turn depends on, the full transitive dependency tree) the same runtime privileges as the application's own code: reading files, making network requests, reading environment variables (including secrets, covered in the previous lesson). A real project's node_modules or equivalent often contains hundreds of packages from dozens of different maintainers, most of whom the development team has never vetted individually — each one is a genuine trust decision, whether or not it was made consciously.

A known vulnerability in a widely-used dependency, discovered after the fact

A library used by thousands of projects is found to have a real,
exploitable vulnerability — maybe it improperly validates input, maybe
it has its OWN SQL-injection-shaped bug internally. Every project using
an affected version is vulnerable too, REGARDLESS of how carefully
that project's OWN code was written — the vulnerability lives in code
the project's team never wrote and may never have read

This is a genuinely common, real pattern: a widely-used package has a vulnerability discovered (sometimes years after the vulnerable code was first published), and every project depending on an affected version inherits that vulnerability automatically — a team's own code can be flawless and still be compromised through a dependency they trusted without ever reading its source.

The fix for known vulnerabilities: automated scanning, run continuously

npm audit                    # scans installed packages against a database of KNOWN vulnerabilities
pip-audit                     # the Python equivalent
Modern CI pipelines run this automatically on every commit/PR, and tools
like Dependabot or Renovate automatically open a pull request the moment
a new vulnerability is disclosed for a dependency already in use —
turning "someone has to remember to check" into a continuously-running,
automated process

npm audit (and its equivalents in other ecosystems) checks every installed package's version against a continuously-updated database of known, publicly disclosed vulnerabilities — running this automatically on every commit, rather than occasionally by hand, is what actually catches a new vulnerability disclosure quickly, since vulnerabilities in already-installed dependencies get discovered and published on an ongoing basis, not just at the moment a project first installs a package.

Supply chain attacks: when the package itself is deliberately malicious

Not every dependency risk is an accidental bug found later — a real,
distinct category: an attacker gains control of a legitimate, trusted
package (compromising a maintainer's account, or a maintainer selling
a popular but unmaintained package to someone with malicious intent)
and publishes a NEW VERSION containing deliberately malicious code —
which then gets installed automatically by every project using a loose
version range that permits picking up the new version

A supply chain attack is fundamentally different from a package having an accidental bug: it's a deliberate, malicious compromise of a package that many projects already trust and depend on — through a compromised maintainer account, a social-engineered handoff of a popular but abandoned package, or a malicious contributor sneaking bad code past review. This is a real, documented, increasingly common attack category specifically because it lets an attacker compromise many downstream projects simultaneously through one single compromised upstream package, rather than attacking each project's own code individually.

Lockfiles as a real, partial defense against automatic pickup of a malicious update

package.json: "some-package": "^2.1.0"   — permits AUTOMATIC minor/patch updates
package-lock.json: "some-package": "2.1.4"  — the EXACT version actually installed, pinned

Using `npm ci` (installing strictly from the lockfile, covered in this
platform's Node.js domain) means a newly-published malicious 2.1.5
DOESN'T get picked up automatically just because 2.1.4 was already
working and locked

A lockfile (covered directly in this platform's Node.js domain for reproducibility) has a real, additional security benefit here: pinning to an exact, already-vetted version means a newly published version — malicious or not — doesn't get installed automatically just because it technically satisfies the version range in package.json. This doesn't eliminate the risk (the lockfile still needs to be deliberately updated eventually, at which point the new version needs its own scrutiny), but it converts "automatically trusts every future release" into "trusts a specific, already-installed version until someone deliberately updates it."

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. Why does installing a dependency mean trusting more than just its advertised functionality?

2. What's the practical difference between an accidental vulnerability in a dependency and a supply chain attack?

3. Why does a lockfile provide a real, partial defense against a newly-published malicious package version?