Configure SCA pipelines using npm audit and Snyk, interpret CVSS scores, manage false positives with policy files, and set up Dependabot for automated security updates.
The average Node.js application has 683 direct and transitive dependencies. Any one of them with a known CVE becomes an attack vector. In 2021, Log4Shell affected millions of applications that had log4j as a transitive dependency — developers discovered it only after the CVE was published.
Software Composition Analysis (SCA) continuously monitors your dependency tree for known vulnerabilities. This lesson builds a production-grade SCA pipeline.
1# Basic audit
2npm audit
3npm audit --audit-level=high # exit 1 on high+
4npm audit --json > audit.json
5
6# Parse JSON output programmatically:
7node -e "
8const a = require('./audit.json');
9const v = a.metadata.vulnerabilities;
10console.log('Critical:', v.critical, '| High:', v.high, '| Moderate:', v.moderate);
11if (v.critical > 0 || v.high > 0) process.exit(1);
12"
13
14# Override a transitive dependency version:
15# In package.json:
16# {
17# 'overrides': {
18# 'qs': '>=6.11.0' // force minimum safe version
19# }
20# }Reading a finding:
1prototype-pollution Critical
2 Vulnerable Versions: <1.7.4
3 Patched in: >=1.7.4
4 Dependency of: express > qs
5 Path: your-app > express > qsFields:
Dependency of: your direct dependency that pulls in the vulnerable packagePatched in: >=1.7.4: upgrade to resolvePatched in: N/A: no fix yet — consider alternatives1# Install and authenticate
2npm install -g snyk
3snyk auth # opens browser for OAuth
4
5# Test and monitor
6snyk test --severity-threshold=high
7snyk monitor --project-name=developerbootstrap-api
8snyk fix # apply safe upgrades1# .snyk — policy file for documented exceptions
2version: v1.25.0
3ignore:
4 SNYK-JS-LODASH-590103:
5 - '*':
6 reason: We only use lodash.get() with hardcoded paths, not user input.
7 expires: '2025-12-31T00:00:00.000Z' # revisit annually
8 created: '2024-01-15'
9
10 npm:hoek:20180212:
11 - 'hapi > hoek':
12 reason: Vulnerable path unreachable in our hapi configuration.
13 expires: '2025-06-01T00:00:00.000Z'Every exception requires:
reason: technical justification why it is not exploitableexpires: date to re-evaluate (max 12 months)1# .github/workflows/dependency-security.yml
2name: Dependency Security
3on:
4 push:
5 branches: [main]
6 pull_request:
7 schedule:
8 - cron: '0 8 * * 1' # Weekly Monday 8am
9
10jobs:
11 sca:
12 runs-on: ubuntu-latest
13 steps:
14 - uses: actions/checkout@v4
15 - uses: actions/setup-node@v4
16 with: { node-version: '20', cache: 'npm' }
17 - run: npm ci
18 - name: npm audit
19 run: |
20 npm audit --json > audit.json 2>/dev/null || true
21 node -e "
22 const a = require('./audit.json');
23 const v = a.metadata.vulnerabilities;
24 if (v.critical > 0 || v.high > 0) {
25 console.error(v.critical + ' critical, ' + v.high + ' high');
26 process.exit(1);
27 }
28 "
29 - name: Snyk test
30 uses: snyk/actions/node@master
31 env:
32 SNYK_TOKEN: SNYK_TOKEN_VALUE
33 with:
34 args: --severity-threshold=high
35 - name: Upload audit
36 if: always()
37 uses: actions/upload-artifact@v4
38 with:
39 name: npm-audit
40 path: audit.json1# .github/dependabot.yml
2version: 2
3updates:
4 - package-ecosystem: npm
5 directory: /
6 schedule:
7 interval: weekly
8 day: monday
9 open-pull-requests-limit: 5
10 reviewers:
11 - org/security-team
12 ignore:
13 - dependency-name: "*"
14 update-types: ["version-update:semver-major"]
15 groups:
16 non-major:
17 update-types: [minor, patch]
18 - package-ecosystem: npm
19 directory: /apps/web
20 schedule: { interval: weekly }
21 - package-ecosystem: npm
22 directory: /apps/api
23 schedule: { interval: weekly }
24 - package-ecosystem: github-actions
25 directory: /
26 schedule: { interval: monthly }Process for Dependabot security PRs:
Not all vulnerabilities are equal. CVSS score bands:
Exploitability factors to consider:
1// Prioritization heuristic:
2// Priority 1: Critical CVSS AND upgradable AND in direct dependency
3// Priority 2: High CVSS AND upgradable
4// Priority 3: Any other exploitable
5
6// In CI: block on critical+high, warn on medium
7npm audit --audit-level=high // fails CI
8npm audit --audit-level=moderate 2>/dev/null | grep moderate // warn onlyA Node.js script that runs npm audit and Snyk, generates a prioritized report, and exits non-zero on critical/high findings.
1// scripts/security-sca.cjs
2// Run: node scripts/security-sca.cjs
3const { execSync } = require('child_process');
4const fs = require('fs');
5
6const REPORT_DIR = 'reports/security';
7fs.mkdirSync(REPORT_DIR, { recursive: true });
8
9let failed = 0;
10console.log('=== Dependency Security Scan ===\n');
11
12// Step 1: npm audit
13console.log('Step 1: npm audit...');
14try {
15 execSync('npm audit --json > ' + REPORT_DIR + '/npm-audit.json 2>/dev/null', { shell: true });
16} catch {
17 // npm audit exits non-zero on findings
18}
19
20if (fs.existsSync(REPORT_DIR + '/npm-audit.json')) {
21 const audit = JSON.parse(fs.readFileSync(REPORT_DIR + '/npm-audit.json', 'utf8'));
22 const v = audit.metadata?.vulnerabilities || {};
23 console.log(' Critical:', v.critical || 0, '| High:', v.high || 0, '| Moderate:', v.moderate || 0);
24 if ((v.critical || 0) > 0 || (v.high || 0) > 0) {
25 console.error(' FAIL: Critical/High vulnerabilities found');
26 failed++;
27 } else {
28 console.log(' PASS: No critical/high vulnerabilities');
29 }
30}
31
32// Step 2: Snyk (optional)
33console.log('\nStep 2: Snyk test...');
34if (process.env.SNYK_TOKEN) {
35 try {
36 execSync('npx snyk test --severity-threshold=high --json > ' + REPORT_DIR + '/snyk.json 2>/dev/null', { shell: true });
37 console.log(' PASS: Snyk found no high/critical issues');
38 } catch {
39 if (fs.existsSync(REPORT_DIR + '/snyk.json')) {
40 const snyk = JSON.parse(fs.readFileSync(REPORT_DIR + '/snyk.json', 'utf8'));
41 const critical = (snyk.vulnerabilities || []).filter(v => v.severity === 'critical').length;
42 const high = (snyk.vulnerabilities || []).filter(v => v.severity === 'high').length;
43 console.error(' FAIL: Snyk found ' + critical + ' critical, ' + high + ' high');
44 failed++;
45 }
46 }
47} else {
48 console.log(' SKIP: SNYK_TOKEN not set');
49}
50
51// Summary
52console.log('\n=== Summary ===');
53if (failed > 0) {
54 console.error('FAIL: ' + failed + ' SCA check(s) failed');
55 process.exit(1);
56} else {
57 console.log('PASS: All SCA checks passed');
58}This script runs npm audit and Snyk sequentially, writing JSON reports to reports/security/. npm audit exits non-zero when vulnerabilities exist, so the try/catch captures the output regardless. The Snyk step is conditional on SNYK_TOKEN being set, making it optional for local runs. Both steps check only critical/high severity, aligning with the CI blocking policy. The script exits with code 1 when any check fails, making it suitable as a CI step.
The next lesson covers SAST with Semgrep — configuring OWASP rulesets, writing custom rules for application-specific patterns, and integrating ESLint security plugins for real-time detection.
Your progress is saved in this browser, on this device only.