Understand security testing techniques (SAST, DAST, SCA, pentest) and how to map the OWASP Top 10 2021 to a layered security testing strategy for Node.js.
Security vulnerabilities cost far more to fix in production than in development. A data breach costs an average of $4.45 million (IBM, 2023). Most breaches exploit known vulnerability classes that automated tools can detect. Security testing is not a one-time event before launch — it is a continuous discipline woven into every development phase.
This lesson introduces the four security testing techniques, maps them to the OWASP Top 10 2021 categories, and shows how they compose into a layered defense.
Security testing uses four complementary techniques. Each finds different vulnerability classes.
SAST (Static Application Security Testing): Analyzes source code without executing it. Finds injection patterns, hardcoded secrets, insecure API usage. Runs in seconds on every commit. Tools: Semgrep, ESLint security plugins, CodeQL.
DAST (Dynamic Application Security Testing): Sends crafted HTTP requests to a running application and observes responses. Finds XSS, injection, auth bypass, and IDOR vulnerabilities at runtime. Tools: OWASP ZAP, Burp Suite.
SCA (Software Composition Analysis): Scans third-party dependencies for known CVEs. Your application is only as secure as its weakest dependency. Tools: npm audit, Snyk, OWASP Dependency-Check.
Penetration Testing: Human-driven adversarial testing against the full running system. Finds logic flaws, business rule violations, and chained vulnerabilities. Required for PCI DSS, SOC 2, and ISO 27001 compliance.
1# Quick security baseline: run all four in under 5 minutes
2
3# SCA: dependency vulnerabilities
4npm audit --audit-level=high
5
6# SAST: code pattern analysis
7npx semgrep --config=auto src/
8
9# DAST: quick ZAP baseline scan (requires running app)
10docker run -t owasp/zap2docker-stable zap-baseline.py -t http://localhost:3000The OWASP Top 10 2021 lists the most critical web application security risks.
A01: Broken Access Control: Users accessing resources they should not. IDOR, privilege escalation, missing authorization checks.
A02: Cryptographic Failures: Weak encryption, HTTP instead of HTTPS, MD5 for passwords.
A03: Injection: SQL injection, NoSQL injection, command injection. Any untrusted data interpreted as a command.
A04: Insecure Design: Missing rate limiting, weak password policy, no account lockout.
A05: Security Misconfiguration: Default credentials, verbose error messages, missing security headers.
A06: Vulnerable and Outdated Components: npm packages with known CVEs.
A07: Identification and Authentication Failures: Weak session tokens, missing MFA, poor password storage.
A08: Software and Data Integrity Failures: CI/CD tampering, insecure deserialization.
A09: Security Logging and Monitoring Failures: No audit log, unmonitored failed logins.
A10: Server-Side Request Forgery (SSRF): Server fetching attacker-controlled URLs.
1// A01 IDOR — WRONG: returns any order regardless of owner
2@Get(':id')
3async getOrder(@Param('id') id: string) {
4 return this.orderService.findById(id);
5}
6
7// CORRECT: enforce ownership
8@Get(':id')
9async getOrder(@Param('id') id: string, @User() user: JwtUser) {
10 const order = await this.orderService.findById(id);
11 if (order.userId !== user.id) throw new ForbiddenException();
12 return order;
13}No single tool catches everything. Defense in depth means running multiple tools at different stages.
Layer 1 — Developer Machine (seconds):
Layer 2 — CI on Every PR (minutes):
Layer 3 — Staging Deployment (tens of minutes):
Layer 4 — Pre-Release (days):
1# .github/workflows/security.yml
2name: Security Scan
3on: [push, pull_request]
4jobs:
5 sast:
6 runs-on: ubuntu-latest
7 steps:
8 - uses: actions/checkout@v4
9 - name: Semgrep SAST
10 uses: semgrep/semgrep-action@v1
11 with:
12 config: >-
13 p/nodejs p/secrets p/owasp-top-ten
14 sca:
15 runs-on: ubuntu-latest
16 steps:
17 - uses: actions/checkout@v4
18 - run: npm ci
19 - run: npm audit --audit-level=highThird-party dependencies are the largest attack surface in most Node.js applications. The average npm package has 77 transitive dependencies.
1# npm audit — built-in, free
2npm audit
3npm audit --audit-level=high # exit 1 if any high+
4npm audit --json > audit-results.json
5npm audit fix # auto-fix non-breaking upgrades
6
7# Snyk — more comprehensive, commercial (free tier)
8npx snyk test
9npx snyk test --severity-threshold=high
10npx snyk monitor # continuous monitoring
11
12# In CI — fail on critical/high:
13steps:
14 - run: |
15 npm audit --json > audit.json 2>/dev/null || true
16 node -e "
17 const a = require('./audit.json');
18 const v = a.metadata.vulnerabilities;
19 if (v.critical > 0 || v.high > 0) {
20 console.error('FAIL: ' + v.critical + ' critical, ' + v.high + ' high');
21 process.exit(1);
22 }
23 console.log('PASS: no critical/high vulnerabilities');
24 "Severity bands:
Node.js has specific dangerous patterns that SAST tools flag and you must always avoid:
1// BANNED: eval() and new Function() — code injection
2eval(userInput);
3new Function(userCode)();
4
5// BANNED: child_process.exec with user input — command injection
6import { exec } from 'child_process';
7exec(`convert ${filename}`); // if filename = '; rm -rf /'
8
9// SAFE: execFile with argument array
10import { execFile } from 'child_process';
11execFile('convert', [sanitizedFilename, outputPath]);
12
13// BANNED: path traversal
14const file = path.join('/uploads', userPath); // '../../../etc/passwd'
15
16// SAFE: resolve and check prefix
17const resolved = path.resolve('/uploads', userPath);
18if (!resolved.startsWith('/uploads/')) throw new ForbiddenException();
19
20// ESLint to catch these at edit time:
21// {
22// "rules": {
23// "no-eval": "error",
24// "no-implied-eval": "error",
25// "no-new-func": "error",
26// "security/detect-child-process": "error"
27// }
28// }A shell script that runs the full security baseline (SCA + SAST + DAST) and produces a consolidated report.
1#!/usr/bin/env bash
2# scripts/security-baseline.sh
3# Run: ./scripts/security-baseline.sh http://localhost:3000
4
5set -euo pipefail
6BASE_URL=${1:-http://localhost:3000}
7REPORT_DIR="reports/security"
8FAILED=0
9mkdir -p "$REPORT_DIR"
10
11echo "=== Security Baseline Scan ==="
12
13# Step 1: npm audit (SCA)
14echo "Step 1/3: Dependency scan (npm audit)..."
15npm audit --json > "$REPORT_DIR/npm-audit.json" 2>/dev/null || true
16node -e "
17const audit = require('./$REPORT_DIR/npm-audit.json');
18const v = audit.metadata.vulnerabilities;
19console.log(' Critical:', v.critical, '| High:', v.high, '| Moderate:', v.moderate);
20if (v.critical > 0 || v.high > 0) { console.error(' FAIL'); process.exitCode = 1; }
21else { console.log(' PASS'); }
22"
23[ $? -ne 0 ] && FAILED=1
24
25# Step 2: Semgrep (SAST)
26echo "Step 2/3: Static analysis (Semgrep)..."
27if command -v semgrep &>/dev/null; then
28 semgrep scan --config=p/nodejs --config=p/secrets --config=p/owasp-top-ten \
29 --json --output="$REPORT_DIR/semgrep.json" --quiet apps/ || true
30 node -e "
31 const r = require('./$REPORT_DIR/semgrep.json');
32 const errors = (r.results || []).filter(f => f.extra.severity === 'ERROR');
33 console.log(' Errors:', errors.length);
34 if (errors.length > 0) process.exitCode = 1;
35 "
36 [ $? -ne 0 ] && FAILED=1
37fi
38
39# Step 3: ZAP baseline (DAST)
40echo "Step 3/3: Dynamic scan (ZAP baseline)..."
41if command -v docker &>/dev/null; then
42 docker run --rm --network host \
43 -v "$(pwd)/$REPORT_DIR:/zap/wrk" \
44 owasp/zap2docker-stable \
45 zap-baseline.py -t "$BASE_URL" -J zap-report.json -I --auto 2>/dev/null || true
46fi
47
48if [ $FAILED -eq 0 ]; then
49 echo "PASS: All security checks passed"
50 exit 0
51else
52 echo "FAIL: One or more security checks failed"
53 exit 1
54fiThis script runs the three automated security layers in sequence. npm audit catches dependency CVEs. Semgrep scans source code for OWASP patterns and dangerous Node.js APIs. ZAP baseline sends passive HTTP requests to the running application. Each step writes JSON output to reports/security/ for artifact upload. Run in CI after staging deployment.
The next lesson covers SQL injection detection — writing automated test payloads, verifying Prisma parameterized queries block injection, and using sqlmap against staging APIs.
Your progress is saved in this browser, on this device only.