Dependency Audit
Read-only analysis patterns for dependency security, license compliance, and health assessment. This skill does NOT auto-upgrade packages -- it helps you understand your dependency landscape and make informed decisions.
Security-First Principles
| Principle | Practice |
|---|
| Audit before merge | Run security checks in CI on every PR |
| Understand before upgrading | Read changelogs, check breaking changes |
| Never blindly force-fix | npm audit fix --force can introduce breaking changes |
| Pin production dependencies | Use exact versions or lock files |
| Verify publisher identity | Check package ownership, download counts, repo activity |
Security Audit Workflow
Step 1: Run the Audit
# npm (built-in)
npm audit
# npm -- JSON output for parsing
npm audit --json
# Yarn
yarn audit
# pnpm
pnpm audit
Step 2: Read the Report
# Example npm audit output
┌───────────────┬──────────────────────────────────────────────┐
│ Severity │ high │
├───────────────┼──────────────────────────────────────────────┤
│ Package │ lodash │
│ Dependency of │ my-library │
│ Path │ my-library > lodash │
│ More info │ https://github.com/advisories/GHSA-xxxx-xxxx │
└───────────────┴──────────────────────────────────────────────┘
Step 3: Assess Each Vulnerability
| Severity | CVSS Score | Action |
|---|
| Critical | 9.0-10.0 | Fix immediately, block deployment |
| High | 7.0-8.9 | Fix within 24 hours |
| Moderate | 4.0-6.9 | Fix within 1 week, assess exploitability |
| Low | 0.1-3.9 | Track, fix in next maintenance window |
| Info | 0 | Informational, no action required |
Step 4: Determine Fix Strategy
# SAFE: See what non-breaking fixes are available
npm audit fix --dry-run
# SAFE: Apply only semver-compatible fixes
npm audit fix
# DANGEROUS: Never run without understanding consequences
# npm audit fix --force <-- CAN INTRODUCE BREAKING CHANGES
# INVESTIGATE: When audit fix doesn't resolve
npm ls <vulnerable-package> # See dependency tree
npm explain <vulnerable-package> # See why it's installed
Step 5: Handle Unresolvable Vulnerabilities
When a vulnerability exists in a transitive dependency with no fix available:
# Check if the vulnerable code path is actually used
# Read the advisory -- is it relevant to your usage?
# Option A: Override the transitive dependency (npm)
# In package.json:
{
"overrides": {
"vulnerable-package": ">=fixed-version"
}
}
# Option B: Override the transitive dependency (yarn)
# In package.json:
{
"resolutions": {
"vulnerable-package": ">=fixed-version"
}
}
# Option C: Exclude from audit (document why)
# Create .nsprc or use npm audit --omit=dev for dev-only deps
Third-Party Scanning Tools
| Tool | Type | Best For |
|---|
npm audit
| Built-in | Quick check, CI integration |
| Snyk | SaaS + CLI | Deep analysis, fix PRs, monitoring |
| Socket.dev | SaaS + CLI | Supply chain attack detection |
| Dependabot | GitHub native | Automated update PRs |
| Renovate | Self-hosted/SaaS | Highly configurable update PRs |
| OSV-Scanner | CLI | Google's vulnerability database |
Snyk CLI Usage
# Install
npm install -g snyk
# Authenticate
snyk auth
# Test for vulnerabilities (read-only)
snyk test
# Monitor project (sends to dashboard)
snyk monitor
# Test a specific package before installing
snyk test <package-name>
OSV-Scanner Usage
# Install
go install github.com/google/osv-scanner/cmd/osv-scanner@latest
# Scan lock file
osv-scanner --lockfile=package-lock.json
# Scan entire directory
osv-scanner -r .
License Compatibility
Common License Types
| License | Type | Can Use In Commercial | Must Open Source Your Code | Must Include License |
|---|
| MIT | Permissive | Yes | No | Yes |
| Apache-2.0 | Permissive | Yes | No | Yes (+ NOTICE file) |
| BSD-2-Clause | Permissive | Yes | No | Yes |
| BSD-3-Clause | Permissive | Yes | No | Yes |
| ISC | Permissive | Yes | No | Yes |
| GPL-2.0 | Copyleft | Yes | Yes (if distributed) | Yes |
| GPL-3.0 | Copyleft | Yes | Yes (if distributed) | Yes |
| LGPL-2.1 | Weak Copyleft | Yes | Only modifications to LGPL code | Yes |
| AGPL-3.0 | Strong Copyleft | Yes | Yes (even for network use) | Yes |
| MPL-2.0 | Weak Copyleft | Yes | Only modified MPL files | Yes |
| Unlicense | Public Domain | Yes | No | No |
| SSPL | Source Available | Check terms | Yes (broad scope) | Yes |
| BSL | Source Available | After change date | Before change date, restricted | Yes |
Compatibility Matrix (Can A Include B?)
| Your Project | MIT | Apache-2.0 | GPL-2.0 | GPL-3.0 | AGPL-3.0 |
|---|
| MIT | Yes | Yes | No | No | No |
| Apache-2.0 | Yes | Yes | No | No | No |
| GPL-2.0 | Yes | No | Yes | No | No |
| GPL-3.0 | Yes | Yes | Yes | Yes | No |
| AGPL-3.0 | Yes | Yes | Yes | Yes | Yes |
Checking Licenses
# List all dependency licenses
npx license-checker --summary
# Check for problematic licenses
npx license-checker --failOn "GPL-2.0;GPL-3.0;AGPL-3.0"
# Export full report
npx license-checker --csv --out licenses.csv
# Production only (skip devDependencies)
npx license-checker --production --summary
CI License Check
# GitHub Actions step
- name: Check licenses
run: |
npx license-checker --production \
--failOn "GPL-2.0;GPL-3.0;AGPL-3.0;SSPL-1.0" \
--excludePackages "known-exception@1.0.0"
Dependency Health Indicators
Red Flags
| Indicator | Risk | Check |
|---|
| No updates in 2+ years | Unmaintained, unpatched vulns | npm view <pkg> time
|
| Single maintainer | Bus factor of 1 | Check GitHub contributors |
| No tests or CI | Quality unknown | Check repo for test config |
| Frequent ownership changes | Possible supply chain risk | Check npm ownership history |
| Typosquat name | Malicious package | Verify exact package name |
| Post-install scripts | Can execute arbitrary code | Check scripts.postinstall
|
| Excessive permissions | Over-scoped access | Review package contents |
| Many open security issues | Known vulnerabilities | Check GitHub issues/advisories |
Health Check Commands
# View package metadata
npm view <package-name>
# Check when last published
npm view <package-name> time --json | jq 'to_entries | sort_by(.value) | last'
# See who maintains it
npm view <package-name> maintainers
# Check download counts (popularity signal)
npm view <package-name> --json | jq '.dist-tags, .versions | length'
# List all postinstall scripts in dependencies
npm ls --json | jq '.. | .scripts?.postinstall? // empty'
# Check package size before installing
npx package-phobia <package-name>
Before Adding a New Dependency
Ask these questions before
npm install
:
Safe Upgrade Strategy
The Read-Analyze-Test-Upgrade Cycle
Never upgrade blindly. Follow this cycle for each upgrade.
Step 1: Identify Available Updates
# See what's outdated (read-only)
npm outdated
# More detail
npm outdated --long
# Output:
# Package Current Wanted Latest Location
# express 4.18.2 4.18.3 5.0.0 my-app
# lodash 4.17.20 4.17.21 4.17.21 my-app
Step 2: Categorize Updates
| Update Type | Version Change | Risk | Example |
|---|
| Patch | x.y.Z
| Low | 4.18.2 -> 4.18.3 (bug fixes) |
| Minor | x.Y.z
| Medium | 4.18.2 -> 4.19.0 (new features) |
| Major | X.y.z
| High | 4.18.2 -> 5.0.0 (breaking changes) |
Step 3: Read Changelogs
# Check the changelog before upgrading
npm view <package-name> repository.url
# Then visit the repo and read CHANGELOG.md or Releases
Step 4: Upgrade in Isolation
# Upgrade one package at a time
npm install <package-name>@<version>
# Run tests immediately
npm test
# Check for type errors (TypeScript)
npx tsc --noEmit
# Check for lint issues
npm run lint
Step 5: Verify and Commit
# Verify the lock file changed as expected
git diff package-lock.json | head -50
# Commit the single upgrade
git add package.json package-lock.json
git commit -m "chore(deps): upgrade <package> to <version>"
Lock File Hygiene
Why Lock Files Matter
| Concern | Without Lock File | With Lock File |
|---|
| Reproducibility | Different versions on each install | Exact same versions every time |
| Security | Can get compromised newer version | Pinned to known-good versions |
| CI reliability | "Works on my machine" | Same everywhere |
| Audit accuracy | Audit results vary | Consistent audit results |
Lock File Rules
| Rule | Rationale |
|---|
| Always commit lock files | Ensures reproducible builds |
Use npm ci in CI, not npm install
| ci respects lock file exactly |
| Review lock file diffs in PRs | Catch unexpected transitive changes |
| Regenerate periodically | Clear accumulated cruft |
| Never manually edit lock files | Use npm/yarn commands instead |
# CORRECT: Clean install from lock file (CI)
npm ci
# CORRECT: Install and update lock file (development)
npm install
# WRONG: Deleting lock file to "fix" issues
# rm package-lock.json && npm install <-- loses pinned versions
Bundle Size Analysis
Measuring Impact
# Check bundle size of a package (before installing)
npx bundlephobia <package-name>
# Analyze your bundle
npx webpack-bundle-analyzer dist/stats.json
# Check what's in node_modules
npx cost-of-modules
# Compare alternatives
npx bundlephobia lodash vs ramda vs lodash-es
Size Budget Rules
| Metric | Budget | Tool |
|---|
| Initial JS bundle | < 200KB gzipped | Webpack/Vite analyzer |
| Single dependency | < 50KB gzipped | bundlephobia |
| Total node_modules | Monitor trend | du -sh node_modules
|
| Install time | < 30s | time npm ci
|
Reducing Bundle Size
| Strategy | Example |
|---|
| Import specific functions | import debounce from 'lodash/debounce' not import { debounce } from 'lodash'
|
| Use tree-shakeable packages | lodash-es instead of lodash
|
| Check for smaller alternatives | date-fns vs moment (tree-shakeable) |
| Lazy load heavy dependencies | const lib = await import('heavy-lib')
|
| Avoid duplicate packages | Check npm ls <pkg> for multiple versions |
CI Integration
Audit in CI Pipeline
name: Dependency Audit
on:
pull_request:
paths:
- 'package.json'
- 'package-lock.json'
schedule:
# Run weekly audit on Monday mornings
- cron: '0 8 * * 1'
jobs:
audit:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- name: Security audit
run: npm audit --audit-level=high
- name: License check
run: npx license-checker --production --failOn "GPL-2.0;GPL-3.0;AGPL-3.0"
- name: Check for outdated (informational)
run: npm outdated || true
Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|
npm audit fix --force
| Introduces breaking major version bumps | Use npm audit fix (safe) and handle majors manually |
| Ignoring audit warnings | Vulnerabilities accumulate | Triage every audit finding, document accepted risks |
"*" or "" version ranges | Installs any version, including malicious | Use exact versions or ^ ranges with lock file |
| Not committing lock file | Non-reproducible builds | Always commit package-lock.json / yarn.lock
|
| Deleting lock file to fix issues | Loses all version pins | Diagnose the actual issue, regenerate carefully |
| Installing without reviewing | Supply chain attack vector | Check package health before npm install
|
| Bulk upgrading all at once | Hard to diagnose breakage | Upgrade one package at a time, test between each |
Running npx with unknown packages | Executes unreviewed code | Verify package before first npx usage |
| Ignoring post-install scripts | Arbitrary code execution | Review scripts, use --ignore-scripts when cautious |
| No CI audit step | Vulnerabilities ship to production | Add npm audit to CI pipeline |
| Using deprecated packages | No security patches | Find maintained alternatives |
devDependencies in production | Larger attack surface, bigger image | Use npm ci --omit=dev for production builds |
Quick Reference: Audit Decision Tree
npm audit reports vulnerability
|
+--> Is it in a devDependency only?
| YES --> Lower priority (not in production)
| NO --> Continue
|
+--> Is there a patch/minor fix available?
| YES --> npm audit fix (safe)
| NO --> Continue
|
+--> Is the vulnerable code path used in your app?
| NO --> Document accepted risk, revisit monthly
| YES --> Continue
|
+--> Is there a major version fix?
| YES --> Read changelog, test upgrade in branch
| NO --> Continue
|
+--> Can you use overrides/resolutions?
| YES --> Pin transitive dep to fixed version
| NO --> Continue
|
+--> Find alternative package or implement inline