Lesson 6 of 9 · 22m
Dependency Auditing: Most of Your Code Isn't Yours
You imported hundreds of thousands of lines of stranger code — lockfiles, scanners, SBOMs, and the attack classes (confusion, typosquatting, install scripts) that audits alone don't cover.
Open your node_modules. A "small" Express app pulls in a few hundred packages and several hundred thousand lines of other people's code — every one a liability you imported, and on npm several ran install scripts on your machine with your permissions. Dependency auditing exists because you can't read your supply chain; you can only inventory it and watch it.
The lockfile is the contract
npm ci # installs exactly what package-lock.json pins — CI, always
npm install # may resolve NEWER versions within semver ranges — dev machinesnpm install in CI quietly re-resolves your dependency tree, meaning the artifact you shipped isn't the tree you scanned. npm ci fails when the lockfile and manifest disagree — that failure is the feature. Same rule everywhere: pip install -r requirements.txt with hashes, cargo build --locked, go build -mod=readonly.
Scan the lockfile, not the intent
osv-scanner --lockfile=package-lock.json # OSV database, multi-ecosystem
npm audit --audit-level=high # the quick npm-native check
pip-audit # Python
cargo audit # RustTwo things juniors miss:
- Findings are mostly transitive. The vulnerable package usually sits three levels deep —
npm ls <pkg>shows who pulled it in, and the fix is often bumping the parent, not the leaf. - Severity isn't reachability. A critical CVE in a library's test helper that never ships is noise; a medium in your request-parsing path is not. Reachability tooling helps, but judgment helps more.
The attack classes nobody scans for
- Typosquatting —
reqestsforrequests. Caught by lockfile discipline and code review, not by CVE feeds. - Dependency confusion — your internal
@acme/utilsdoesn't exist on the public registry, so an attacker publishes it there with a higher version and your build prefers it. Fix: scope packages, publish a placeholder, or force a private registry in.npmrc. - Install scripts —
preinstall/postinstallrun arbitrary code at install time.npm ci --ignore-scriptsin CI kills the whole class for packages that don't need it.
SBOMs: the inventory that pays later
syft . -o cyclonedx-json=sbom.json # or: npx @cyclonedx/cyclonedx-npmThe day the next log4shell drops, the question is "do we ship it, anywhere?" An SBOM per release answers in minutes. Without one you're grepping lockfiles across forty repos at 2am — I've done that weekend and don't recommend it.
Field notes
- Pair scanning with automated update PRs (Renovate, Dependabot) grouped by ecosystem — a scanner producing findings nobody can land is just a guilt generator.
npm audit fix --forcehappily bumps major versions and breaks your app. Read the diff it proposes.- Vendored dependencies (a checked-in
third_party/) are invisible to lockfile scanners — scan the files, not just the manifests.