references/common-vulnerabilities.md
# Common TypeScript/Node.js Vulnerability Patterns
## Injection Vulnerabilities
### SQL Injection via Template Literals
```typescript
// ❌ Vulnerable
async function searchUsers(name: string) {
return db.query(`SELECT * FROM users WHERE name LIKE '%${name}%'`);
}
// ✅ Fix: Parameterized query
async function searchUsers(name: string) {
return db.query('SELECT * FROM users WHERE name LIKE $1', [`%${name}%`]);
}
```
### NoSQL Injection (MongoDB)
```typescript
// ❌ Vulnerable: User input as query operator
app.post('/login', async (req, res) => {
const user = await User.findOne({
email: req.body.email,
password: req.body.password, // Could be { $ne: '' }
});
});
// ✅ Fix: Validate and cast input types
app.post('/login', async (req, res) => {
const { email, password } = loginSchema.parse(req.body); // Zod ensures strings
const user = await User.findOne({ email });
if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
});
```
### Command Injection
```typescript
// ❌ Vulnerable: Shell command with user input
import { exec } from 'child_process';
exec(`convert ${filename} output.png`);
// ✅ Fix: Use execFile (no shell interpretation)
import { execFile } from 'child_process';
execFile('convert', [filename, 'output.png']);
```
### Regex Denial of Service (ReDoS)
```typescript
// ❌ Vulnerable: Catastrophic backtracking
const emailRegex = /^([a-zA-Z0-9]+)*@([a-zA-Z0-9]+)*\.([a-zA-Z]+)$/;
// ✅ Fix: Use non-backtracking patterns or Zod
import { z } from 'zod';
const emailSchema = z.string().email();
```
## Cross-Site Scripting (XSS)
### React dangerouslySetInnerHTML
```tsx
// ❌ Vulnerable
<div dangerouslySetInnerHTML={{ __html: userContent }} />
// ✅ Fix: Sanitize with DOMPurify
import DOMPurify from 'isomorphic-dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} />
```
### Server-Side Template Injection
```typescript
// ❌ Vulnerable: String interpolation in HTML response
app.get('/greeting', (req, res) => {
res.send(`<h1>Hello ${req.query.name}</h1>`);
});
// ✅ Fix: Escape HTML entities
import { escape } from 'html-escaper';
app.get('/greeting', (req, res) => {
res.send(`<h1>Hello ${escape(req.query.name as string)}</h1>`);
});
```
## Authentication Vulnerabilities
### Timing Attack on Comparison
```typescript
// ❌ Vulnerable: Early return leaks timing information
function verifyApiKey(provided: string, stored: string): boolean {
return provided === stored; // Timing attack possible
}
// ✅ Fix: Constant-time comparison
import { timingSafeEqual } from 'crypto';
function verifyApiKey(provided: string, stored: string): boolean {
const a = Buffer.from(provided);
const b = Buffer.from(stored);
if (a.length !== b.length) return false;
return timingSafeEqual(a, b);
}
```
### JWT Algorithm Confusion
```typescript
// ❌ Vulnerable: Accepts any algorithm
jwt.verify(token, publicKey); // Attacker can use 'none' or switch HS/RS
// ✅ Fix: Restrict algorithms
jwt.verify(token, publicKey, {
algorithms: ['RS256'], // Only accept expected algorithm
});
```
### Insecure Password Reset
```typescript
// ❌ Vulnerable: Predictable token
const resetToken = Math.random().toString(36);
// ✅ Fix: Cryptographically secure token with expiration
import { randomBytes } from 'crypto';
const resetToken = randomBytes(32).toString('hex');
const tokenHash = createHash('sha256').update(resetToken).digest('hex');
const expires = new Date(Date.now() + 3600_000); // 1 hour
```
## Path Traversal
```typescript
// ❌ Vulnerable: User controls file path
app.get('/files', (req, res) => {
const filePath = path.join('/uploads', req.query.name as string);
res.sendFile(filePath);
});
// Attack: ?name=../../etc/passwd
// ✅ Fix: Validate path stays within allowed directory
import path from 'path';
app.get('/files', (req, res) => {
const basePath = path.resolve('/uploads');
const filePath = path.resolve(basePath, req.query.name as string);
if (!filePath.startsWith(basePath)) {
return res.status(400).json({ error: 'Invalid file path' });
}
res.sendFile(filePath);
});
```
## Prototype Pollution
```typescript
// ❌ Vulnerable: Deep merge with user input
function deepMerge(target: any, source: any) {
for (const key in source) {
if (typeof source[key] === 'object') {
target[key] = deepMerge(target[key] || {}, source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
// Attack: { "__proto__": { "isAdmin": true } }
// ✅ Fix: Block prototype keys
function safeMerge(target: Record<string, unknown>, source: Record<string, unknown>) {
for (const key of Object.keys(source)) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue;
}
if (typeof source[key] === 'object' && source[key] !== null) {
target[key] = safeMerge(
(target[key] as Record<string, unknown>) || {},
source[key] as Record<string, unknown>,
);
} else {
target[key] = source[key];
}
}
return target;
}
```
## Information Disclosure
### Error Messages
```typescript
// ❌ Vulnerable: Exposes internals
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
res.status(500).json({
error: err.message,
stack: err.stack, // Full stack trace
query: req.query, // Request details
});
});
// ✅ Fix: Generic error response
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
logger.error('Unhandled error', { error: err.message, stack: err.stack });
res.status(500).json({
error: 'Internal server error',
requestId: req.id,
});
});
```
### Enumeration via Timing or Response
```typescript
// ❌ Vulnerable: Different responses for existing vs non-existing users
app.post('/login', async (req, res) => {
const user = await findUser(req.body.email);
if (!user) return res.status(404).json({ error: 'User not found' });
if (!await bcrypt.compare(req.body.password, user.hash)) {
return res.status(401).json({ error: 'Wrong password' });
}
});
// ✅ Fix: Same response for all failures
app.post('/login', async (req, res) => {
const user = await findUser(req.body.email);
const isValid = user && await bcrypt.compare(req.body.password, user.hash);
if (!isValid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
});
```
## Denial of Service
### Event Loop Blocking
```typescript
// ❌ Vulnerable: Blocking the event loop
app.get('/hash', (req, res) => {
const hash = crypto.pbkdf2Sync(req.query.data, 'salt', 100000, 64, 'sha512');
res.json({ hash: hash.toString('hex') });
});
// ✅ Fix: Async operation
app.get('/hash', async (req, res) => {
const hash = await new Promise((resolve, reject) => {
crypto.pbkdf2(req.query.data, 'salt', 100000, 64, 'sha512', (err, key) => {
if (err) reject(err);
else resolve(key.toString('hex'));
});
});
res.json({ hash });
});
```
### Unbounded Input
```typescript
// ❌ Vulnerable: No limit on request body size
app.use(express.json());
// ✅ Fix: Limit body size
app.use(express.json({ limit: '10kb' }));
```
references/dependency-security.md
# Dependency Security and Supply Chain Guide
## npm Audit
### Running Security Audits
```bash
# Check for known vulnerabilities
npm audit
# Get machine-readable output
npm audit --json
# Fix automatically where possible
npm audit fix
# Fix with major version bumps (review carefully)
npm audit fix --force
# Only show critical/high severity
npm audit --audit-level=high
```
### CI Pipeline Integration
```yaml
# GitHub Actions example
- name: Security audit
run: |
npm audit --audit-level=high
if [ $? -ne 0 ]; then
echo "::error::npm audit found high/critical vulnerabilities"
exit 1
fi
```
## Dependency Management Best Practices
### Lock Files
Always commit `package-lock.json` to ensure reproducible builds and prevent supply chain attacks.
```bash
# Install from lock file only (CI environments)
npm ci
# Never use in CI:
# npm install ← Can resolve different versions
```
### Minimal Dependencies
Every dependency is an attack surface. Review before adding:
```bash
# Check package details before installing
npm info <package> | grep -E 'description|homepage|repository|maintainers'
# Check download stats and maintenance
npx npm-check-updates --doctor
```
### Questions Before Adding a Dependency
1. Is this actively maintained? (check last commit date, open issues)
2. How many transitive dependencies does it add? (`npm ls <package>`)
3. Can I implement this with the standard library instead?
4. Does the maintainer have 2FA enabled? (check npm profile)
5. Is there a more popular/trusted alternative?
## Automated Dependency Scanning
### GitHub Dependabot
```yaml
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
reviewers:
- "security-team"
labels:
- "dependencies"
- "security"
```
### Socket.dev
Detects supply chain attacks that npm audit misses:
- Typosquatting (similar package names)
- Install scripts that execute code during `npm install`
- Obfuscated code
- Network access during install
- Excessive permissions
### Snyk
Provides deeper vulnerability analysis and fix PRs:
```bash
# Install and authenticate
npm install -g snyk
snyk auth
# Test for vulnerabilities
snyk test
# Monitor project continuously
snyk monitor
```
## Supply Chain Attack Vectors
### Typosquatting
Attackers publish packages with names similar to popular ones.
```bash
# ❌ Easy mistake
npm install expres # Not "express"
npm install lodahs # Not "lodash"
# ✅ Double-check package names
npm info express | head -5
```
### Install Script Attacks
Malicious packages run code during `npm install`.
```bash
# Check for install scripts before installing
npm show <package> scripts
# Audit install scripts in your dependencies
npx can-i-ignore-scripts
```
### Dependency Confusion
Attackers publish public packages with the same name as internal packages.
When a package name is unclaimed on the public registry, anyone can publish
malicious code under that name — never reference unclaimed package names in
documentation or install commands.
**Prevention:**
```bash
# Example: configure .npmrc to resolve scoped packages from a private registry
# (using Microsoft's real public registry as illustration only)
registry=https://registry.npmjs.org/
@microsoft:registry=https://npm.pkg.github.com/
```
Always verify that any package name referenced in docs or scripts is either
claimed by your organization or points to an explicit local path (e.g. `./script-name`).
## Reviewing New Dependencies
### Security Checklist for New Packages
- [ ] **Popularity**: >1000 weekly downloads or well-known organization
- [ ] **Maintenance**: Updated within last 6 months
- [ ] **License**: Compatible with your project (MIT, Apache-2.0, BSD)
- [ ] **Dependencies**: Minimal transitive dependencies
- [ ] **Install scripts**: No suspicious preinstall/postinstall scripts
- [ ] **Source code**: Repository matches published package
- [ ] **Maintainers**: Multiple maintainers, 2FA enabled
- [ ] **TypeScript**: Has type definitions (DefinitelyTyped or built-in)
- [ ] **CVE history**: Check for past vulnerabilities on Snyk/NVD
### Evaluating Package Health
```bash
# Check package size and dependencies
npx packagephobia <package>
# Check bundle size impact
npx bundlephobia <package>
# List all transitive dependencies
npm ls <package> --all
# Check for known vulnerabilities
npx audit-ci --config audit-ci.json
```
## Responding to Vulnerabilities
### Severity Assessment
| Severity | Response Time | Action |
|----------|--------------|--------|
| Critical | Immediate | Patch or remove dependency |
| High | Within 1 week | Upgrade to fixed version |
| Medium | Within 1 month | Plan upgrade in next sprint |
| Low | Next quarter | Include in regular maintenance |
### When a Fix is Not Available
1. **Check if vulnerability is exploitable** in your usage context
2. **Apply workarounds** (disable affected feature, add input validation)
3. **Fork and patch** the dependency if critical
4. **Replace** with an alternative package
5. **Accept risk** with documented justification (last resort)
```typescript
// Document accepted risk in code
// SECURITY: CVE-2024-XXXXX in package-x v1.2.3
// Risk accepted: We don't use the affected XML parser feature
// Review date: 2024-06-01
// Ticket: JIRA-1234
```
## Environment Variable Security
### Validation at Startup
```typescript
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
API_KEY: z.string().min(16),
PORT: z.coerce.number().default(3000),
});
const env = envSchema.parse(process.env);
export default env;
```
### .env Files
```bash
# .gitignore — ALWAYS ignore env files
.env
.env.local
.env.*.local
# .env.example — template without secrets (DO commit this)
NODE_ENV=development
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=<generate-with-openssl-rand-hex-32>
```
### Prevent Client Exposure
```typescript
// Next.js: Only NEXT_PUBLIC_ vars are exposed to client
// ❌ Exposed to client bundle
NEXT_PUBLIC_API_KEY=secret // Don't put secrets here!
// ✅ Server-only (not in client bundle)
DATABASE_URL=postgresql://...
JWT_SECRET=...
```
references/input-validation.md
# Input Validation Patterns
## Zod Schema Validation
```typescript
import { z } from 'zod';
// Comprehensive user creation schema
const createUserSchema = z.object({
name: z.string().min(1).max(100).trim(),
email: z.string().email().max(254).toLowerCase(),
password: z.string()
.min(12, 'Password must be at least 12 characters')
.regex(/[A-Z]/, 'Must contain uppercase letter')
.regex(/[a-z]/, 'Must contain lowercase letter')
.regex(/[0-9]/, 'Must contain a number'),
role: z.enum(['user', 'editor']).default('user'),
});
// API endpoint with validation
app.post('/api/users', async (req, res) => {
const result = createUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.flatten() });
}
const user = await createUser(result.data);
res.status(201).json(user);
});
```
## NestJS Validation with class-validator
```typescript
import { IsEmail, IsString, MinLength, Matches, IsEnum } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(1)
@MaxLength(100)
name: string;
@IsEmail()
@MaxLength(254)
email: string;
@IsString()
@MinLength(12)
@Matches(/[A-Z]/, { message: 'Must contain uppercase letter' })
@Matches(/[a-z]/, { message: 'Must contain lowercase letter' })
@Matches(/[0-9]/, { message: 'Must contain a number' })
password: string;
@IsEnum(['user', 'editor'])
role?: 'user' | 'editor';
}
```
## Validation Error Handling
```typescript
// Consistent error response format
interface ValidationError {
field: string;
message: string;
}
function handleValidation(errors: ZodError): ValidationError[] {
return errors.errors.map(err => ({
field: err.path.join('.'),
message: err.message,
}));
}
// Return structured 400 response
res.status(400).json({
error: 'Validation failed',
details: handleValidation(result.error),
});
```
references/owasp-typescript.md
# OWASP Top 10 for TypeScript/Node.js
## A01: Broken Access Control
### Risk
Users acting outside their intended permissions. The most common web application vulnerability.
### TypeScript/Node.js Patterns
```typescript
// ❌ Vulnerable: No authorization check
app.get('/api/users/:id/profile', async (req, res) => {
const profile = await db.user.findUnique({ where: { id: req.params.id } });
res.json(profile); // Any user can access any profile
});
// ✅ Secure: Proper authorization
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
if (req.user.id !== req.params.id && req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
const profile = await db.user.findUnique({ where: { id: req.params.id } });
res.json(profile);
});
```
### Review Checklist
- [ ] All endpoints enforce authentication
- [ ] Authorization checks verify resource ownership
- [ ] Admin endpoints restricted by role
- [ ] Direct object references validated against user permissions
- [ ] CORS restrictive (not `origin: '*'`)
- [ ] JWT tokens validated on every request
## A02: Cryptographic Failures
### Risk
Exposure of sensitive data due to weak or missing encryption.
### TypeScript/Node.js Patterns
```typescript
// ❌ Vulnerable: MD5 for passwords, no salt
import { createHash } from 'crypto';
const hash = createHash('md5').update(password).digest('hex');
// ✅ Secure: bcrypt with proper cost factor
import bcrypt from 'bcrypt';
const hash = await bcrypt.hash(password, 12);
const isValid = await bcrypt.compare(input, hash);
```
### Review Checklist
- [ ] Passwords hashed with bcrypt or argon2 (cost factor ≥ 10)
- [ ] Sensitive data encrypted at rest (AES-256)
- [ ] TLS/HTTPS enforced for all communications
- [ ] No sensitive data in JWT payloads
- [ ] Strong random values from `crypto.randomBytes`
- [ ] API keys not in source code or client bundles
## A03: Injection
### Risk
Untrusted data sent to an interpreter as part of a command or query.
### TypeScript/Node.js Patterns
```typescript
// ❌ SQL Injection
const result = await db.query(`SELECT * FROM users WHERE name = '${name}'`);
// ✅ Parameterized query
const result = await db.query('SELECT * FROM users WHERE name = $1', [name]);
// ❌ Command Injection
const { exec } = require('child_process');
exec(`ls ${userInput}`); // Shell injection
// ✅ Safe subprocess
const { execFile } = require('child_process');
execFile('ls', [userInput]); // No shell interpretation
```
### Review Checklist
- [ ] All SQL queries use parameterized statements or ORM
- [ ] No `eval()`, `new Function()`, or `vm.runInNewContext()` with user input
- [ ] `child_process.exec` not used with user input (use `execFile` or `spawn`)
- [ ] Template literals not used for SQL, shell commands, or LDAP queries
- [ ] NoSQL queries don't accept operator objects from user input
## A04: Insecure Design
### Review Checklist
- [ ] Business logic has rate limiting for abuse-prone operations
- [ ] Multi-step operations validate state at each step
- [ ] Error messages don't reveal system internals
- [ ] Sensitive operations require re-authentication
## A05: Security Misconfiguration
### TypeScript/Node.js Patterns
```typescript
// ❌ Misconfigured: Debug enabled, permissive CORS
app.use(cors());
app.use(errorHandler({ showStack: true }));
// ✅ Secure configuration
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(','),
credentials: true,
}));
app.use(helmet());
if (process.env.NODE_ENV === 'production') {
app.set('trust proxy', 1);
}
```
### Review Checklist
- [ ] Debug mode disabled in production
- [ ] Security headers configured (helmet.js)
- [ ] Error responses don't include stack traces in production
- [ ] Default accounts/passwords removed
- [ ] Unnecessary features disabled
- [ ] `NODE_ENV=production` set in production
## A06: Vulnerable and Outdated Components
### Review Checklist
- [ ] `npm audit` reports no critical/high vulnerabilities
- [ ] Dependencies regularly updated
- [ ] Lock file (`package-lock.json`) committed
- [ ] No unnecessary dependencies
- [ ] Dependabot or Renovate configured
## A07: Identification and Authentication Failures
### TypeScript/Node.js Patterns
```typescript
// ❌ Weak session management
app.use(session({
secret: 'secret',
cookie: {},
}));
// ✅ Secure session configuration
app.use(session({
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 15 * 60 * 1000, // 15 minutes
},
}));
```
### Review Checklist
- [ ] Passwords require minimum 12 characters with complexity
- [ ] Account lockout after failed attempts
- [ ] Session tokens rotated after login
- [ ] JWT expiration set (short-lived: 15 min)
- [ ] Refresh tokens stored securely (HttpOnly cookie)
- [ ] Multi-factor authentication for sensitive operations
## A08: Software and Data Integrity Failures
### Review Checklist
- [ ] CI/CD pipeline integrity verified
- [ ] Dependencies verified against known good hashes
- [ ] Subresource integrity (SRI) for external scripts
- [ ] Serialization/deserialization validated (no `JSON.parse` of untrusted data without schema validation)
## A09: Security Logging and Monitoring Failures
### TypeScript/Node.js Patterns
```typescript
// ✅ Security event logging
logger.warn('Authentication failed', {
ip: req.ip,
email: req.body.email, // Log identifier, NOT password
userAgent: req.headers['user-agent'],
timestamp: new Date().toISOString(),
});
```
### Review Checklist
- [ ] Failed login attempts logged with IP and user agent
- [ ] Authorization failures logged
- [ ] Sensitive data NOT included in logs (passwords, tokens, PII)
- [ ] Log injection prevented (sanitize user input in logs)
- [ ] Monitoring alerts for anomalous patterns
## A10: Server-Side Request Forgery (SSRF)
### TypeScript/Node.js Patterns
```typescript
// ❌ SSRF vulnerable
app.get('/fetch', async (req, res) => {
const response = await fetch(req.query.url as string); // User controls URL
res.json(await response.json());
});
// ✅ SSRF prevention
const ALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com'];
app.get('/fetch', async (req, res) => {
const url = new URL(req.query.url as string);
if (!ALLOWED_DOMAINS.includes(url.hostname)) {
return res.status(400).json({ error: 'Domain not allowed' });
}
if (url.protocol !== 'https:') {
return res.status(400).json({ error: 'HTTPS required' });
}
const response = await fetch(url.toString());
res.json(await response.json());
});
```
### Review Checklist
- [ ] Server-side HTTP requests don't use user-controlled URLs
- [ ] URL allowlists for external requests
- [ ] Internal network addresses blocked (127.0.0.1, 10.x, 172.16.x, 192.168.x)
- [ ] DNS rebinding prevention
references/security-headers.md
# Security Headers and Configuration
## Express Security Configuration
### Vulnerable: Missing security headers and permissive CORS
```typescript
const app = express();
app.use(cors()); // Allows all origins
```
### Secure: Comprehensive security configuration
```typescript
import helmet from 'helmet';
import cors from 'cors';
import rateLimit from 'express-rate-limit';
const app = express();
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
},
},
hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
}));
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') ?? [],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));
app.use(rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: false,
}));
```
## NestJS Security Module
```typescript
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
@Module({
imports: [
HelmetModule,
ThrottlerModule.forRoot([{
ttl: 60000,
limit: 10,
}]),
],
})
export class SecurityModule {}
```
## Cookie Security Flags
```typescript
// Always set these flags on sensitive cookies
res.cookie('sessionId', sessionId, {
httpOnly: true, // Prevents JavaScript access
secure: true, // HTTPS only
sameSite: 'strict', // CSRF protection
maxAge: 3600000, // 1 hour expiration
path: '/',
});
```
references/xss-prevention.md
# XSS Prevention Patterns
## React Components
### Vulnerable: dangerouslySetInnerHTML without sanitization
```tsx
function Comment({ content }: { content: string }) {
return <div dangerouslySetInnerHTML={{ __html: content }} />;
}
```
### Secure: DOMPurify sanitization
```tsx
import DOMPurify from 'isomorphic-dompurify';
function Comment({ content }: { content: string }) {
const sanitized = DOMPurify.sanitize(content, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href', 'target', 'rel'],
});
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}
```
### Better: Markdown renderer instead of raw HTML
```tsx
import ReactMarkdown from 'react-markdown';
function Comment({ content }: { content: string }) {
return <ReactMarkdown>{content}</ReactMarkdown>;
}
```
## Server-Side Template Engines
```typescript
// Vulnerable: Template injection
const template = `Hello ${userInput}`; // User input directly in template
// Secure: Use template engines with auto-escaping
import Handlebars from 'handlebars';
const safeTemplate = Handlebars.compile('Hello {{name}}');
const result = safeTemplate({ name: userInput }); // Auto-escaped
```
SKILL.md
---
name: typescript-security-review
description: Provides security review capability for TypeScript/Node.js applications, validates code against XSS, injection, CSRF, JWT/OAuth2 flaws, dependency CVEs, and secrets exposure. Use when performing security audits, before deployment, reviewing authentication/authorization implementations, or ensuring OWASP compliance for Express, NestJS, and Next.js. Triggers on "security review", "check for security issues", "TypeScript security audit".
allowed-tools: Read, Edit, Grep, Glob, Bash
---
# TypeScript Security Review
## Overview
Security review for TypeScript/Node.js applications. Evaluates code against OWASP Top 10, framework-specific patterns, and production-readiness criteria. Findings are classified by severity (Critical, High, Medium, Low) with remediation examples. Delegates to the `typescript-security-expert` agent for deep analysis.
## When to Use
- Performing security audits on TypeScript/Node.js codebases
- Reviewing authentication and authorization implementations (JWT, OAuth2, Passport.js)
- Checking for common vulnerabilities (XSS, injection, CSRF, path traversal)
- Validating input validation and sanitization logic
- Reviewing dependency security (npm audit, known CVEs)
- Checking secrets management and environment variable handling
- Assessing API security (rate limiting, CORS, security headers)
- Reviewing Express, NestJS, or Next.js security configurations
- Before deploying to production or after significant code changes
- Compliance checks (GDPR, HIPAA, SOC2 data handling requirements)
## Instructions
1. **Identify Scope**: Determine which files and modules are under review. Prioritize authentication, authorization, data handling, API endpoints, and configuration files. Use `grep` to find security-sensitive patterns (`eval`, `exec`, `innerHTML`, password handling, JWT operations).
**Checkpoint**: Verify at least 3 security-sensitive files/modules identified before proceeding.
2. **Check Authentication & Authorization**: Review JWT implementation (signing algorithm, expiration, refresh tokens), OAuth2/OIDC integration, session management, password hashing (bcrypt/argon2), and multi-factor authentication. Verify protected routes enforce authentication.
**Checkpoint**: Use `grep` to confirm all route handlers have auth guards or middleware applied.
3. **Scan for Injection Vulnerabilities**: Check for SQL/NoSQL injection in database queries, command injection in `exec`/`spawn`, template injection, and LDAP injection. Verify parameterized queries and input validation.
**Checkpoint**: Use `grep` to confirm all database queries use parameterization — no string concatenation with user input.
4. **Review Input Validation**: Check API inputs validated with Zod, Joi, or class-validator. Verify schema completeness — proper type constraints, length limits, format validation. Check for validation bypass paths.
**Checkpoint**: Verify all public API endpoints have corresponding validation schemas.
5. **Assess XSS Prevention**: Review React components for `dangerouslySetInnerHTML` usage, check Content Security Policy headers, verify HTML sanitization for user-generated content. See `references/xss-prevention.md` for detailed patterns.
**Checkpoint**: Use `grep` to confirm any `dangerouslySetInnerHTML` usage has sanitization via DOMPurify or equivalent.
6. **Check Secrets Management**: Scan for hardcoded credentials, API keys, secrets in source code. Verify `.env` files are gitignored, secrets accessed through proper management services.
**Checkpoint**: Run `grep -r "password\|secret\|api.*key\|token" --include="*.ts"` to identify potential secrets in code.
7. **Review Dependency Security**: Run `npm audit` or check `package-lock.json` for known vulnerabilities. Identify outdated dependencies with CVEs. Check for unnecessary dependencies.
**Checkpoint**: Verify `npm audit` results are reviewed and critical vulnerabilities addressed.
8. **Evaluate Security Headers & Configuration**: Check helmet.js or manual security header configuration. Review CORS policy, rate limiting, HTTPS enforcement, cookie security flags (HttpOnly, Secure, SameSite), and CSP. See `references/security-headers.md` for configuration examples.
**Checkpoint**: Use `grep` to confirm helmet or equivalent security headers are applied globally.
9. **Produce Security Report**: Generate structured report with severity-classified findings, remediation guidance with code examples, and security posture summary.
**Feedback Loop**: If Critical or High vulnerabilities found, re-scan related modules for similar patterns before finalizing. Use `grep` to identify if the same vulnerability pattern exists elsewhere.
## Examples
### JWT Security Review
```typescript
// ❌ Critical: Weak JWT configuration
import jwt from 'jsonwebtoken';
const SECRET = 'mysecret123'; // Hardcoded weak secret
function generateToken(user: User) {
return jwt.sign({ id: user.id, role: user.role }, SECRET);
// Missing expiration, weak secret, no algorithm specification
}
// ✅ Secure: Proper JWT configuration
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET || JWT_SECRET.length < 32) {
throw new Error('JWT_SECRET must be set and at least 32 characters');
}
function generateToken(user: User): string {
return jwt.sign(
{ sub: user.id }, // Minimal claims, no sensitive data
JWT_SECRET,
{
algorithm: 'HS256',
expiresIn: '15m',
issuer: 'my-app',
audience: 'my-app-client',
}
);
}
function verifyToken(token: string): JwtPayload {
return jwt.verify(token, JWT_SECRET, {
algorithms: ['HS256'], // Restrict accepted algorithms
issuer: 'my-app',
audience: 'my-app-client',
}) as JwtPayload;
}
```
### SQL Injection Prevention
```typescript
// ❌ Critical: SQL injection vulnerability
async function findUser(email: string) {
const result = await db.query(
`SELECT * FROM users WHERE email = '${email}'`
);
return result.rows[0];
}
// ✅ Secure: Parameterized query
async function findUser(email: string) {
const result = await db.query(
'SELECT id, name, email FROM users WHERE email = $1',
[email]
);
return result.rows[0];
}
// ✅ Secure: ORM with type-safe queries (Drizzle example)
async function findUser(email: string) {
return db.select({
id: users.id,
name: users.name,
email: users.email,
})
.from(users)
.where(eq(users.email, email))
.limit(1);
}
```
See `references/xss-prevention.md` for XSS patterns and `references/security-headers.md` for security headers configuration.
## Review Output Format
Structure all security review findings as follows:
### 1. Security Posture Summary
Overall security assessment score (1-10) with key observations and risk level.
### 2. Critical Vulnerabilities (Immediate Action)
Issues that can be exploited to compromise the system, steal data, or cause unauthorized access.
### 3. High Priority (Address Within 30 Days)
Security misconfigurations, missing protections, or vulnerabilities requiring near-term remediation.
### 4. Medium Priority (Address Within 90 Days)
Issues that reduce security posture but have mitigating factors or limited exploitability.
### 5. Low Priority (Next Cycle)
Security improvements, hardening recommendations, and defense-in-depth enhancements.
### 6. Positive Security Observations
Well-implemented security patterns and practices to acknowledge.
### 7. Remediation Roadmap
Prioritized action items with code examples for the most critical fixes.
## Best Practices
- Validate all inputs at the API boundary — never trust client-side validation alone
- Use parameterized queries or ORMs — never concatenate user input into queries
- Store secrets in environment variables or secret managers — never in source code
- Apply the principle of least privilege for database accounts, API keys, and IAM roles
- Enable security headers (helmet.js) and restrict CORS to known origins
- Implement rate limiting on all public-facing endpoints
- Hash passwords with bcrypt or argon2 — never use MD5/SHA for passwords
- Set cookie flags: `HttpOnly`, `Secure`, `SameSite=Strict`
- Use `npm audit` in CI pipelines to catch dependency vulnerabilities
- Log security events (failed logins, permission denials) without logging sensitive data
## Constraints and Warnings
- Security review is not a substitute for professional penetration testing
- Focus on code-level vulnerabilities — infrastructure security is out of scope
- Respect the project's framework — provide framework-specific remediation guidance
- Do not log, print, or expose discovered secrets — report their location only
- Dependency vulnerabilities should be assessed for actual exploitability, not just presence
- Security recommendations must be practical — consider implementation effort vs risk reduction
## References
See the `references/` directory for detailed security documentation:
- `references/owasp-typescript.md` — OWASP Top 10 mapped to TypeScript/Node.js patterns
- `references/common-vulnerabilities.md` — Common vulnerability patterns and remediation
- `references/dependency-security.md` — Dependency scanning and supply chain security
- `references/xss-prevention.md` — XSS prevention patterns for React and server-side
- `references/security-headers.md` — Security headers and CORS configuration examples
- `references/input-validation.md` — Input validation patterns with Zod and class-validator