Skip to main content

Dependency Security and Vulnerability Scanning in JavaScript

Created: May 8, 2026 Larry Qu 8 min read

Dependency security is critical for application safety. This article covers vulnerability scanning, supply chain security, and dependency management best practices.

Introduction

Dependency security provides:

  • Vulnerability detection
  • Supply chain protection
  • Risk management
  • Compliance
  • Proactive defense

Understanding dependency security helps you:

  • Identify vulnerabilities
  • Manage dependencies
  • Reduce risk
  • Maintain compliance
  • Protect applications

npm Audit

Running npm Audit

// ✅ Good: Use npm audit to scan dependencies
// Command line:
// npm audit                    # Show vulnerabilities
// npm audit --json            # JSON output
// npm audit fix               # Auto-fix vulnerabilities
// npm audit fix --force       # Force fix (may break compatibility)

class NPMAuditScanner {
  static async runAudit() {
    const { execSync } = require('child_process');

    try {
      const result = execSync('npm audit --json', { encoding: 'utf-8' });
      return JSON.parse(result);
    } catch (error) {
      console.error('Error running npm audit:', error.message);
      return null;
    }
  }

  static async analyzeResults(auditResult) {
    if (!auditResult || !auditResult.vulnerabilities) {
      return { safe: true, vulnerabilities: [] };
    }

    const vulnerabilities = [];

    for (const [pkg, details] of Object.entries(auditResult.vulnerabilities)) {
      vulnerabilities.push({
        package: pkg,
        severity: details.severity,
        fixAvailable: details.fixAvailable,
        description: details.via[0]?.title || 'Unknown'
      });
    }

    return {
      safe: vulnerabilities.length === 0,
      vulnerabilities,
      metadata: auditResult.metadata
    };
  }

  static async generateReport() {
    const auditResult = await this.runAudit();
    const analysis = await this.analyzeResults(auditResult);

    console.log('=== NPM Audit Report ===');
    console.log(`Total vulnerabilities: ${analysis.vulnerabilities.length}`);

    if (analysis.vulnerabilities.length > 0) {
      console.log('\nVulnerabilities:');
      analysis.vulnerabilities.forEach(vuln => {
        console.log(`  - ${vuln.package} (${vuln.severity})`);
        console.log(`    ${vuln.description}`);
      });
    }

    return analysis;
  }
}

// Usage
const report = await NPMAuditScanner.generateReport();

Interpreting Audit Results

// ✅ Good: Understand audit results
class AuditResultInterpreter {
  static getSeverityLevel(severity) {
    const levels = {
      'critical': 4,
      'high': 3,
      'moderate': 2,
      'low': 1
    };
    return levels[severity] || 0;
  }

  static shouldBlock(vulnerability) {
    // Block critical and high severity vulnerabilities
    const severity = this.getSeverityLevel(vulnerability.severity);
    return severity >= 3;
  }

  static getRecommendation(vulnerability) {
    if (vulnerability.fixAvailable) {
      return 'Update to patched version';
    }

    if (vulnerability.severity === 'critical') {
      return 'Replace package or implement workaround';
    }

    return 'Monitor for updates';
  }

  static analyzeVulnerability(vuln) {
    return {
      package: vuln.package,
      severity: vuln.severity,
      shouldBlock: this.shouldBlock(vuln),
      recommendation: this.getRecommendation(vuln),
      fixAvailable: vuln.fixAvailable
    };
  }
}

// Usage
const vulnerability = {
  package: 'lodash',
  severity: 'high',
  fixAvailable: true
};

const analysis = AuditResultInterpreter.analyzeVulnerability(vulnerability);
console.log(analysis);
// {
//   package: 'lodash',
//   severity: 'high',
//   shouldBlock: true,
//   recommendation: 'Update to patched version',
//   fixAvailable: true
// }

Snyk Security Scanning

Snyk Integration

// ✅ Good: Use Snyk for advanced vulnerability scanning
// Installation:
// npm install -g snyk
// snyk auth
// snyk test
// snyk monitor

class SnykScanner {
  static async testDependencies() {
    const { execSync } = require('child_process');

    try {
      const result = execSync('snyk test --json', { encoding: 'utf-8' });
      return JSON.parse(result);
    } catch (error) {
      // Snyk returns non-zero exit code if vulnerabilities found
      try {
        return JSON.parse(error.stdout);
      } catch {
        console.error('Error running Snyk:', error.message);
        return null;
      }
    }
  }

  static async monitorDependencies() {
    const { execSync } = require('child_process');

    try {
      const result = execSync('snyk monitor --json', { encoding: 'utf-8' });
      return JSON.parse(result);
    } catch (error) {
      console.error('Error monitoring with Snyk:', error.message);
      return null;
    }
  }

  static async generateReport() {
    const testResult = await this.testDependencies();

    if (!testResult) {
      return { safe: true };
    }

    const vulnerabilities = testResult.vulnerabilities || [];
    const issues = testResult.issues || [];

    return {
      safe: vulnerabilities.length === 0 && issues.length === 0,
      vulnerabilities: vulnerabilities.map(v => ({
        package: v.package,
        severity: v.severity,
        fixAvailable: v.isUpgradable,
        description: v.title
      })),
      issues: issues.length
    };
  }
}

// Usage
const report = await SnykScanner.generateReport();
console.log(report);

Dependency Management

Dependency Pinning

// ✅ Good: Pin dependencies to specific versions
// package.json
{
  "dependencies": {
    "express": "4.18.2",
    "lodash": "4.17.21",
    "axios": "1.4.0"
  },
  "devDependencies": {
    "jest": "29.5.0",
    "eslint": "8.40.0"
  }
}

// Use package-lock.json for reproducible installs
// npm ci (instead of npm install)

class DependencyManager {
  static getPinnedDependencies() {
    const packageJson = require('./package.json');
    return {
      dependencies: packageJson.dependencies,
      devDependencies: packageJson.devDependencies
    };
  }

  static validateVersions() {
    const deps = this.getPinnedDependencies();

    for (const [pkg, version] of Object.entries(deps.dependencies)) {
      // Ensure exact version (no ^, ~, *)
      if (version.match(/^[\^~*]/)) {
        console.warn(`Warning: ${pkg} uses flexible versioning: ${version}`);
      }
    }
  }
}

// Usage
DependencyManager.validateVersions();

Dependency Updates

// ✅ Good: Manage dependency updates safely
class DependencyUpdater {
  static async checkForUpdates() {
    const { execSync } = require('child_process');

    try {
      const result = execSync('npm outdated --json', { encoding: 'utf-8' });
      return JSON.parse(result);
    } catch (error) {
      console.error('Error checking for updates:', error.message);
      return {};
    }
  }

  static async updateDependencies(options = {}) {
    const { execSync } = require('child_process');

    const {
      minor = true,
      patch = true,
      major = false,
      dev = true
    } = options;

    try {
      if (patch) {
        execSync('npm update --save');
      }

      if (minor) {
        execSync('npm update --save');
      }

      if (major) {
        console.warn('Major version updates require manual review');
      }

      console.log('Dependencies updated successfully');
    } catch (error) {
      console.error('Error updating dependencies:', error.message);
    }
  }

  static async generateUpdateReport() {
    const outdated = await this.checkForUpdates();

    const report = {
      total: Object.keys(outdated).length,
      packages: []
    };

    for (const [pkg, details] of Object.entries(outdated)) {
      report.packages.push({
        package: pkg,
        current: details.current,
        wanted: details.wanted,
        latest: details.latest,
        type: details.type
      });
    }

    return report;
  }
}

// Usage
const report = await DependencyUpdater.generateUpdateReport();
console.log(report);

Supply Chain Security

Verify Package Integrity

// ✅ Good: Verify package integrity
const crypto = require('crypto');
const fs = require('fs');

class PackageIntegrityChecker {
  static calculateHash(filePath) {
    const content = fs.readFileSync(filePath);
    return crypto.createHash('sha256').update(content).digest('hex');
  }

  static verifyPackageSignature(packageName, expectedHash) {
    // In production, verify against npm registry
    // npm view ${packageName} dist.shasum
    const packagePath = `./node_modules/${packageName}/package.json`;

    try {
      const actualHash = this.calculateHash(packagePath);
      return actualHash === expectedHash;
    } catch (error) {
      console.error('Error verifying package:', error.message);
      return false;
    }
  }

  static async verifyAllPackages() {
    const packageJson = require('./package.json');
    const allDeps = {
      ...packageJson.dependencies,
      ...packageJson.devDependencies
    };

    const results = [];

    for (const [pkg, version] of Object.entries(allDeps)) {
      try {
        const packagePath = `./node_modules/${pkg}/package.json`;
        const hash = this.calculateHash(packagePath);

        results.push({
          package: pkg,
          version,
          hash,
          verified: true
        });
      } catch (error) {
        results.push({
          package: pkg,
          version,
          verified: false,
          error: error.message
        });
      }
    }

    return results;
  }
}

// Usage
const verification = await PackageIntegrityChecker.verifyAllPackages();
console.log(verification);

Detect Malicious Packages

// ✅ Good: Detect potentially malicious packages
class MaliciousPackageDetector {
  static async checkPackageReputation(packageName) {
    // Use npm registry API
    try {
      const response = await fetch(`https://registry.npmjs.org/${packageName}`);
      const data = await response.json();

      return {
        name: packageName,
        downloads: data['dist-tags']?.latest ? 'high' : 'low',
        maintainers: data.maintainers?.length || 0,
        lastPublished: data.time?.modified,
        versions: Object.keys(data.versions || {}).length
      };
    } catch (error) {
      console.error('Error checking package reputation:', error.message);
      return null;
    }
  }

  static isSuspicious(packageInfo) {
    // Red flags for suspicious packages
    const flags = [];

    if (!packageInfo.maintainers || packageInfo.maintainers < 1) {
      flags.push('No maintainers');
    }

    if (packageInfo.versions < 2) {
      flags.push('Very few versions');
    }

    // Check if recently published
    const lastPublished = new Date(packageInfo.lastPublished);
    const daysSincePublish = (Date.now() - lastPublished) / (1000 * 60 * 60 * 24);

    if (daysSincePublish < 7) {
      flags.push('Recently published');
    }

    return flags.length > 0 ? flags : null;
  }

  static async scanDependencies() {
    const packageJson = require('./package.json');
    const allDeps = {
      ...packageJson.dependencies,
      ...packageJson.devDependencies
    };

    const results = [];

    for (const pkg of Object.keys(allDeps)) {
      const info = await this.checkPackageReputation(pkg);
      const suspicious = this.isSuspicious(info);

      results.push({
        package: pkg,
        suspicious: suspicious ? true : false,
        flags: suspicious || []
      });
    }

    return results;
  }
}

// Usage
const scan = await MaliciousPackageDetector.scanDependencies();
console.log(scan);

CI/CD Integration

Automated Vulnerability Scanning

// ✅ Good: Automated scanning in CI/CD
// .github/workflows/security.yml
/*
name: Security Scan

on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: '18'
      - run: npm ci
      - run: npm audit
      - run: npm run snyk-test
      - run: npm run security-check
*/

class SecurityCheckScript {
  static async runAllChecks() {
    console.log('Running security checks...\n');

    const checks = [
      { name: 'npm audit', fn: this.runNpmAudit },
      { name: 'Snyk scan', fn: this.runSnykScan },
      { name: 'Dependency check', fn: this.runDependencyCheck }
    ];

    let allPassed = true;

    for (const check of checks) {
      console.log(`Running ${check.name}...`);
      const passed = await check.fn();

      if (!passed) {
        allPassed = false;
        console.log(`❌ ${check.name} failed\n`);
      } else {
        console.log(`✅ ${check.name} passed\n`);
      }
    }

    return allPassed;
  }

  static async runNpmAudit() {
    const { execSync } = require('child_process');

    try {
      execSync('npm audit --audit-level=moderate');
      return true;
    } catch {
      return false;
    }
  }

  static async runSnykScan() {
    const { execSync } = require('child_process');

    try {
      execSync('snyk test --severity-threshold=high');
      return true;
    } catch {
      return false;
    }
  }

  static async runDependencyCheck() {
    // Custom dependency checks
    return true;
  }
}

// Usage
const allPassed = await SecurityCheckScript.runAllChecks();
process.exit(allPassed ? 0 : 1);

Best Practices

  1. Run regular audits:
    // ✅ Good
    // npm audit in CI/CD pipeline
    // npm audit fix regularly
    
    // ❌ Bad
    // Never run npm audit
    ```javascript
    
  2. Pin dependencies:
    // ✅ Good
    // "express": "4.18.2"
    
    // ❌ Bad
    // "express": "^4.18.2"
    ```javascript
    
  3. Monitor for updates:
    // ✅ Good
    // npm outdated
    // Snyk monitor
    
    // ❌ Bad
    // Never check for updates
    ```javascript
    

Common Mistakes

  1. Ignoring audit warnings:
    // ❌ Bad
    // npm audit fix --force (without review)
    
    // ✅ Good
    // Review and test before updating
    ```javascript
    
  2. Using flexible versioning:
    // ❌ Bad
    // "express": "^4.18.0"
    
    // ✅ Good
    // "express": "4.18.2"
    ```javascript
    
  3. Not monitoring dependencies:
    // ❌ Bad
    // No dependency monitoring
    
    // ✅ Good
    // snyk monitor
    

Summary

Dependency security is essential. Key takeaways:

  • Run npm audit regularly
  • Use Snyk for advanced scanning
  • Pin dependency versions
  • Monitor for updates
  • Verify package integrity
  • Detect malicious packages
  • Automate security checks
  • Review before updating

Next Steps

Resources

Comments

Share this article

Scan to read on mobile