Why Scan Dependencies?
Most modern applications consist of more third-party code than first-party code. A typical Node.js project can have thousands of transitive dependencies, each potentially containing known vulnerabilities. Dependency vulnerability scanning automatically identifies packages in your dependency tree that have published security advisories (CVEs). Without automated scanning, you are relying on luck to avoid deploying known vulnerabilities.
Vulnerability databases like the GitHub Advisory Database, NVD (National Vulnerability Database), and Snyk's vulnerability database continuously catalog known issues. Scanning tools compare your installed packages against these databases and alert you when a match is found, along with severity ratings and remediation advice.
npm audit
The simplest scanning tool is built right into npm. Running npm audit checks your installed packages against the npm security advisory database and reports vulnerabilities with severity levels (low, moderate, high, critical) and fix recommendations.
// Running npm audit in different contexts
// Basic audit
// $ npm audit
// Only show high and critical vulnerabilities
// $ npm audit --audit-level=high
// JSON output for programmatic processing
// $ npm audit --json
// Fix automatically where possible
// $ npm audit fix
// Force fix (may include breaking changes)
// $ npm audit fix --force
// Automated audit check script
import { execSync } from "child_process";
function runSecurityAudit(): {
vulnerabilities: Record<string, number>;
passed: boolean;
} {
try {
const result = execSync("npm audit --json", {
encoding: "utf-8",
timeout: 60000,
});
const audit = JSON.parse(result);
const vulns = audit.metadata.vulnerabilities;
const hasCritical = vulns.critical > 0 || vulns.high > 0;
return {
vulnerabilities: vulns,
passed: !hasCritical,
};
} catch (error: any) {
// npm audit exits with non-zero when vulnerabilities found
const audit = JSON.parse(error.stdout);
return {
vulnerabilities: audit.metadata.vulnerabilities,
passed: false,
};
}
}
// Use in CI to block deployments
const result = runSecurityAudit();
if (!result.passed) {
console.error("Security audit failed:", result.vulnerabilities);
process.exit(1);
}
GitHub Dependabot Configuration
GitHub Dependabot automatically creates pull requests to update vulnerable dependencies. It monitors the GitHub Advisory Database and your project's dependency files, then creates PRs with minimal version bumps to fix known vulnerabilities.
// .github/dependabot.yml
/*
version: 2
updates:
# npm dependencies
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "09:00"
timezone: "America/New_York"
open-pull-requests-limit: 10
reviewers:
- "security-team"
labels:
- "dependencies"
- "security"
# Group minor and patch updates together
groups:
production-dependencies:
patterns:
- "*"
update-types:
- "minor"
- "patch"
# Ignore specific packages (e.g., ones that need manual testing)
ignore:
- dependency-name: "next"
update-types: ["version-update:semver-major"]
# GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
# Docker base images
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
*/
CI/CD Integration
// GitHub Actions workflow for security scanning
/*
name: Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 8 * * 1' # Weekly Monday at 8am
jobs:
dependency-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run npm audit
run: npm audit --audit-level=high
- name: Check for known vulnerabilities with Snyk
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
- name: License compliance check
run: npx license-checker --failOn 'GPL-3.0;AGPL-3.0;UNLICENSED'
*/
// Programmatic vulnerability tracking
interface VulnerabilityReport {
packageName: string;
currentVersion: string;
fixedVersion: string;
severity: "low" | "moderate" | "high" | "critical";
cveId: string;
description: string;
}
async function generateVulnerabilityReport(): Promise<VulnerabilityReport[]> {
const auditResult = execSync("npm audit --json", { encoding: "utf-8" });
const audit = JSON.parse(auditResult);
return Object.entries(audit.vulnerabilities || {}).map(
([name, data]: [string, any]) => ({
packageName: name,
currentVersion: data.range,
fixedVersion: data.fixAvailable?.version || "No fix available",
severity: data.severity,
cveId: data.via?.[0]?.url || "N/A",
description: data.via?.[0]?.title || "Unknown vulnerability",
})
);
}
Vulnerability Scanning Strategy
- Scan on every CI run: Make vulnerability scanning a required CI check that blocks merges.
- Set severity thresholds: Block on high and critical, warn on moderate, track low.
- Automate updates: Use Dependabot or Renovate for automated PRs with version updates.
- Track exceptions: If you must accept a vulnerability temporarily, document it with an expiry date.
- Monitor continuously: New vulnerabilities are published daily. Scan weekly even without code changes.
- Check transitive dependencies: Most vulnerabilities are in transitive (indirect) dependencies.
Security Warning: False Sense of Security
- Zero vulnerabilities does not mean secure: Scanning only finds KNOWN vulnerabilities. Zero-day exploits will not appear in any database.
- Not all vulnerabilities are exploitable: A vulnerability in a function you never call may not affect you, but tracking this requires manual analysis.
- Scanning is not a substitute for code review: Automated tools miss logic flaws, misconfigurations, and novel attack vectors.