We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.

This lesson's interactive features are locked, please to keep using them

Risks of Dependencies

Modern apps depend on code their teams didn't write... and every dependency brings a tree of transitive dependencies, install scripts, maintainers, and release processes into your security boundary.

A "transitive dependency" is a package that your app doesn't directly depend on, but is a dependency of one of your direct dependencies. A transitive dependency can be several levels deep, and if you're working in TypeScript, you probably have hundreds of them.

An outdated package might have a known vulnerability. A compromised maintainer might publish a malicious release. An install script can read developer or CI environment variables before your app ever runs! That's a lot of trust for one npm install.

A version range in package.json describes which releases the package manager may select:

{
  "dependencies": {
    "express": "^5.2.1"
  }
}

The caret allows compatible updates within the same major version. That doesn't mean every npm ci silently chooses a new release. When a committed package-lock.json is present, npm ci installs the exact locked dependency tree and fails if it disagrees with package.json.

The range mostly matters when the lockfile is created from scratch or deliberately updated. Without a committed lockfile, different installations can resolve different trees. With one, builds are reproducible.

A lockfile makes installation reproducible. It does not make the locked code safe or current.

Before adding a dependency, ask whether its capability is really worth the extra code and attack surface. Good engineers don't solve all their problems with npm install!