Front-end Engineering Lab

Supply Chain Security

Protect your application from compromised dependencies

Supply Chain Security

Supply chain attacks target your dependencies. Malicious code in npm packages can steal secrets, mine crypto, or backdoor your application. This guide covers how to protect against these threats.

The Problem

Your Application

  Dependencies (1000+)

  Transitive Dependencies (10,000+)

  One compromised package = Entire app compromised

Real attacks:

  • event-stream (2018): 2M downloads/week, injected Bitcoin stealer
  • ua-parser-js (2021): 8M downloads/week, crypto miner injected
  • colors/faker (2022): Maintainer sabotaged own packages
  • node-ipc (2022): Deleted files on Russian IPs

Risk Assessment

Check Your Dependencies

# Count dependencies
npm ls --all | wc -l

# Check for vulnerabilities
npm audit

# Detailed audit
npm audit --json > audit.json

Dependency Graph

# Visualize dependency tree
npm ls --all

# Check specific package dependencies
npm ls lodash --all

Protection Strategies

1. Lock Files

# ✅ Always commit lock files
git add package-lock.json
git commit -m "Add lock file"

# Use npm ci in production (respects lock file exactly)
npm ci

# Don't use npm install in production
# (may install different versions)

2. Audit Regularly

# Check for vulnerabilities
npm audit

# Fix automatically (be careful!)
npm audit fix

# Fix only production dependencies
npm audit fix --only=prod

# See what will be fixed
npm audit fix --dry-run

3. Snyk

# Install Snyk
npm install -g snyk

# Authenticate
snyk auth

# Test for vulnerabilities
snyk test

# Monitor continuously
snyk monitor

# Test and get actionable advice
snyk test --json | snyk-to-html -o report.html

4. GitHub Dependabot

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
    reviewers:
      - "your-team"
    labels:
      - "dependencies"

5. Renovate Bot

{
  "extends": ["config:base"],
  "packageRules": [
    {
      "matchUpdateTypes": ["minor", "patch"],
      "matchCurrentVersion": "!/^0/",
      "automerge": true
    }
  ],
  "vulnerabilityAlerts": {
    "enabled": true
  }
}

6. npm/yarn Security Plugins

# npm audit signatures
npm audit signatures

# Yarn audit
yarn audit

# pnpm audit
pnpm audit

Package Vetting

Before Installing

# Check package info
npm info package-name

# Check downloads
npm info package-name dist.tarball

# Check repository
npm repo package-name

# Check issues
npm bugs package-name

Red Flags

🚩 No repository link
🚩 Very few downloads (< 1000/week)
🚩 No recent updates (> 2 years)
🚩 Suspicious maintainer
🚩 Recently transferred ownership
🚩 Typosquatting name (lodash vs loadash)
🚩 No tests
🚩 Obfuscated code
🚩 Requests unusual permissions
🚩 Large bundle size for simple task

Vetting Checklist

// .npmrc - require 2FA for publishes
audit=true
fund=false
package-lock=true
save-exact=true

Dependency Pinning

Exact Versions

{
  "dependencies": {
    "react": "18.2.0",           // ✅ Exact version
    "lodash": "^4.17.21",         // ❌ Allows minor updates
    "express": "~4.18.2"          // ❌ Allows patch updates
  }
}
# Install with exact versions
npm install --save-exact package-name

# Configure npm to always use exact versions
npm config set save-exact true

Shrinkwrap

# Create shrinkwrap (like package-lock but published to npm)
npm shrinkwrap

# Result: npm-shrinkwrap.json (takes precedence over package-lock.json)

Subresource Integrity for CDN

<!-- ✅ Verify integrity of CDN packages -->
<script
  src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.production.min.js"
  integrity="sha384-..."
  crossorigin="anonymous"
></script>

Private Registry

Verdaccio

# Install Verdaccio (private npm registry)
npm install -g verdaccio

# Start registry
verdaccio

# Configure npm to use it
npm set registry http://localhost:4873/

# Publish private packages
npm publish --registry http://localhost:4873/

Artifactory / Nexus

# Configure npm to use private registry
npm config set registry https://artifactory.company.com/api/npm/npm-repo/

# Use for specific scope
npm config set @company:registry https://artifactory.company.com/api/npm/npm-repo/

Scanning Tools

Socket.dev

# Install
npm install -g socket

# Scan dependencies
socket npx create-react-app my-app
socket install package-name

OSSF Scorecard

# Check project security practices
docker run -e GITHUB_AUTH_TOKEN=token gcr.io/openssf/scorecard:stable \
  --repo=github.com/owner/repo

npm-audit-ci

# GitHub Actions
name: Security Audit
on: [push, pull_request]

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npm audit --audit-level=moderate

Automated Scanning in CI/CD

GitHub Actions

name: Dependency Security Scan

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 0 * * 0'  # Weekly

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: 18
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run npm audit
        run: npm audit --audit-level=high
      
      - name: Run Snyk
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high
      
      - name: Check for known vulnerabilities
        run: |
          npx audit-ci --high

Pre-commit Hooks

# Install husky
npm install --save-dev husky

# Setup pre-commit hook
npx husky install
npx husky add .git/hooks/pre-commit "npm audit"
{
  "scripts": {
    "prepare": "husky install",
    "precommit": "npm audit --audit-level=moderate"
  }
}

License Compliance

# Check licenses
npx license-checker

# Check for problematic licenses
npx license-checker --onlyAllow 'MIT;ISC;Apache-2.0;BSD-3-Clause'

# Generate report
npx license-checker --json > licenses.json

Best Practices

1. Minimal Dependencies

# Before adding a dependency, ask:
# - Can I implement this myself?
# - Is there a smaller alternative?
# - Is this actively maintained?
# - Does it have security vulnerabilities?

# Check bundle size impact
npm install --save package-name
npx bundlephobia package-name

2. Regular Updates

# Check outdated packages
npm outdated

# Update interactively
npx npm-check -u

# Update all (careful!)
npm update

3. Review Changes

# Before updating, review changelogs
npm info package-name

# Check diff
git diff package-lock.json

4. Separate Dev Dependencies

{
  "dependencies": {
    "react": "^18.2.0"        // Production only
  },
  "devDependencies": {
    "typescript": "^5.0.0"    // Development only
  }
}

5. Two-Factor Authentication

# Enable 2FA on npm
npm profile enable-2fa auth-and-writes

# Require 2FA for organization
# (npm.com → Organizations → Settings → Require 2FA)

Monitoring

Runtime Monitoring

// Detect suspicious behavior at runtime
import crypto from 'crypto';

const originalFetch = global.fetch;

global.fetch = async (...args) => {
  const url = args[0]?.toString();
  
  // Detect suspicious domains
  if (url && isSuspiciousDomain(url)) {
    console.error('⚠️ Suspicious network request:', url);
    // Alert security team
  }
  
  return originalFetch(...args);
};

function isSuspiciousDomain(url: string): boolean {
  const suspiciousPatterns = [
    /pastebin\.com/,
    /\d+\.\d+\.\d+\.\d+/,  // IP addresses
    /\.tk$/,  // Free TLDs
  ];
  
  return suspiciousPatterns.some(pattern => pattern.test(url));
}

File System Monitoring

// Detect file operations
import fs from 'fs';

const originalWriteFile = fs.writeFile;

fs.writeFile = (path, data, ...args) => {
  console.warn('File write detected:', path);
  
  // Block writes to sensitive paths
  if (path.includes('/etc/') || path.includes('.ssh')) {
    throw new Error('Unauthorized file access');
  }
  
  return originalWriteFile(path, data, ...args);
};

Incident Response

If Compromised Package Detected

# 1. Immediately remove
npm uninstall compromised-package

# 2. Check if already exploited
git log --all --grep='compromised-package'

# 3. Rotate secrets
# - API keys
# - Database passwords
# - Encryption keys
# - SSH keys

# 4. Audit logs
# - Check for unauthorized access
# - Review network traffic
# - Check file system changes

# 5. Report
# - npm security team: security@npmjs.com
# - GitHub Advisory: github.com/advisories

Security Checklist

Lock files committed (package-lock.json)
npm ci in production (not npm install)
Regular audits (npm audit weekly)
Dependabot/Renovate enabled
2FA enabled on npm account
Exact versions for critical deps
License compliance checked
CI/CD scanning configured
Minimal dependencies philosophy
Runtime monitoring for suspicious behavior

Common Pitfalls

Not committing lock files: Inconsistent deps
Commit package-lock.json

npm install in production: Wrong versions
npm ci in production

Never updating: Vulnerable to exploits
Regular updates + audits

No 2FA: Account compromise
Enable 2FA on npm

Trusting all packages: Supply chain attack
Vet packages before installing

Supply chain security is critical—vet packages, audit regularly, and minimize dependencies!

On this page