$ -weight: 500;">npm -weight: 500;">install helmet
-weight: 500;">npm -weight: 500;">install helmet
-weight: 500;">npm -weight: 500;">install helmet
import express from 'express';
import helmet from 'helmet'; const app = express(); // Apply all helmet defaults — do this before any other middleware
app.use(helmet());
import express from 'express';
import helmet from 'helmet'; const app = express(); // Apply all helmet defaults — do this before any other middleware
app.use(helmet());
import express from 'express';
import helmet from 'helmet'; const app = express(); // Apply all helmet defaults — do this before any other middleware
app.use(helmet());
app.use( helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'nonce-GENERATED_NONCE'"], styleSrc: ["'self'", 'https://fonts.googleapis.com'], imgSrc: ["'self'", 'data:', 'https:'], connectSrc: ["'self'"], fontSrc: ["'self'", 'https://fonts.gstatic.com'], objectSrc: ["'none'"], mediaSrc: ["'self'"], frameSrc: ["'none'"], }, }, })
);
app.use( helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'nonce-GENERATED_NONCE'"], styleSrc: ["'self'", 'https://fonts.googleapis.com'], imgSrc: ["'self'", 'data:', 'https:'], connectSrc: ["'self'"], fontSrc: ["'self'", 'https://fonts.gstatic.com'], objectSrc: ["'none'"], mediaSrc: ["'self'"], frameSrc: ["'none'"], }, }, })
);
app.use( helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'nonce-GENERATED_NONCE'"], styleSrc: ["'self'", 'https://fonts.googleapis.com'], imgSrc: ["'self'", 'data:', 'https:'], connectSrc: ["'self'"], fontSrc: ["'self'", 'https://fonts.gstatic.com'], objectSrc: ["'none'"], mediaSrc: ["'self'"], frameSrc: ["'none'"], }, }, })
);
-weight: 500;">npm -weight: 500;">install express-rate-limit
-weight: 500;">npm -weight: 500;">install express-rate-limit
-weight: 500;">npm -weight: 500;">install express-rate-limit
import rateLimit from 'express-rate-limit'; // Global rate limit — applies to all routes
const globalLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // max 100 requests per window standardHeaders: true, // include RateLimit-* headers in response legacyHeaders: false, // -weight: 500;">disable X-RateLimit-* headers message: { -weight: 500;">status: 429, error: 'Too many requests. Please try again later.', retryAfter: '15 minutes', },
}); // Strict limiter for auth endpoints
const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 10, // only 10 login attempts per 15 minutes message: { -weight: 500;">status: 429, error: 'Too many authentication attempts.' },
}); app.use(globalLimiter);
app.post('/auth/login', authLimiter, loginHandler);
app.post('/auth/register', authLimiter, registerHandler);
app.post('/auth/forgot-password', authLimiter, forgotPasswordHandler);
import rateLimit from 'express-rate-limit'; // Global rate limit — applies to all routes
const globalLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // max 100 requests per window standardHeaders: true, // include RateLimit-* headers in response legacyHeaders: false, // -weight: 500;">disable X-RateLimit-* headers message: { -weight: 500;">status: 429, error: 'Too many requests. Please try again later.', retryAfter: '15 minutes', },
}); // Strict limiter for auth endpoints
const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 10, // only 10 login attempts per 15 minutes message: { -weight: 500;">status: 429, error: 'Too many authentication attempts.' },
}); app.use(globalLimiter);
app.post('/auth/login', authLimiter, loginHandler);
app.post('/auth/register', authLimiter, registerHandler);
app.post('/auth/forgot-password', authLimiter, forgotPasswordHandler);
import rateLimit from 'express-rate-limit'; // Global rate limit — applies to all routes
const globalLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // max 100 requests per window standardHeaders: true, // include RateLimit-* headers in response legacyHeaders: false, // -weight: 500;">disable X-RateLimit-* headers message: { -weight: 500;">status: 429, error: 'Too many requests. Please try again later.', retryAfter: '15 minutes', },
}); // Strict limiter for auth endpoints
const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 10, // only 10 login attempts per 15 minutes message: { -weight: 500;">status: 429, error: 'Too many authentication attempts.' },
}); app.use(globalLimiter);
app.post('/auth/login', authLimiter, loginHandler);
app.post('/auth/register', authLimiter, registerHandler);
app.post('/auth/forgot-password', authLimiter, forgotPasswordHandler);
-weight: 500;">npm -weight: 500;">install rate-limit-redis ioredis
-weight: 500;">npm -weight: 500;">install rate-limit-redis ioredis
-weight: 500;">npm -weight: 500;">install rate-limit-redis ioredis
import { RedisStore } from 'rate-limit-redis';
import Redis from 'ioredis'; const redis = new Redis(process.env.REDIS_URL); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100, store: new RedisStore({ sendCommand: (...args) => redis.call(...args), }),
});
import { RedisStore } from 'rate-limit-redis';
import Redis from 'ioredis'; const redis = new Redis(process.env.REDIS_URL); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100, store: new RedisStore({ sendCommand: (...args) => redis.call(...args), }),
});
import { RedisStore } from 'rate-limit-redis';
import Redis from 'ioredis'; const redis = new Redis(process.env.REDIS_URL); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100, store: new RedisStore({ sendCommand: (...args) => redis.call(...args), }),
});
-weight: 500;">npm -weight: 500;">install cors
-weight: 500;">npm -weight: 500;">install cors
-weight: 500;">npm -weight: 500;">install cors
import cors from 'cors'; const allowedOrigins = [ 'https://yourapp.com', 'https://www.yourapp.com', process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null,
].filter(Boolean); app.use( cors({ origin: (origin, callback) => { // Allow requests with no origin (Postman, -weight: 500;">curl, mobile apps) if (!origin) return callback(null, true); if (allowedOrigins.includes(origin)) return callback(null, true); callback(new Error(`CORS policy: origin ${origin} not allowed`)); }, credentials: true, // allow cookies to be sent cross-origin methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'], exposedHeaders: ['X-Total-Count', 'X-Request-ID'], maxAge: 86400, // cache preflight for 24 hours })
);
import cors from 'cors'; const allowedOrigins = [ 'https://yourapp.com', 'https://www.yourapp.com', process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null,
].filter(Boolean); app.use( cors({ origin: (origin, callback) => { // Allow requests with no origin (Postman, -weight: 500;">curl, mobile apps) if (!origin) return callback(null, true); if (allowedOrigins.includes(origin)) return callback(null, true); callback(new Error(`CORS policy: origin ${origin} not allowed`)); }, credentials: true, // allow cookies to be sent cross-origin methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'], exposedHeaders: ['X-Total-Count', 'X-Request-ID'], maxAge: 86400, // cache preflight for 24 hours })
);
import cors from 'cors'; const allowedOrigins = [ 'https://yourapp.com', 'https://www.yourapp.com', process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null,
].filter(Boolean); app.use( cors({ origin: (origin, callback) => { // Allow requests with no origin (Postman, -weight: 500;">curl, mobile apps) if (!origin) return callback(null, true); if (allowedOrigins.includes(origin)) return callback(null, true); callback(new Error(`CORS policy: origin ${origin} not allowed`)); }, credentials: true, // allow cookies to be sent cross-origin methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'], exposedHeaders: ['X-Total-Count', 'X-Request-ID'], maxAge: 86400, // cache preflight for 24 hours })
);
-weight: 500;">npm -weight: 500;">install zod
-weight: 500;">npm -weight: 500;">install zod
-weight: 500;">npm -weight: 500;">install zod
import { z } from 'zod'; // Define schemas as your source of truth
const CreateUserSchema = z.object({ email: z.string().email('Invalid email format'), password: z .string() .min(12, 'Password must be at least 12 characters') .regex(/[A-Z]/, 'Must contain at least one uppercase letter') .regex(/[0-9]/, 'Must contain at least one number') .regex(/[^A-Za-z0-9]/, 'Must contain at least one special character'), name: z.string().min(2).max(100).trim(), role: z.enum(['user', 'admin']).default('user'), age: z.number().int().min(13).max(120).optional(),
}); // Middleware that validates and transforms
function validate(schema) { return (req, res, next) => { const result = schema.safeParse(req.body); if (!result.success) { return res.-weight: 500;">status(400).json({ error: 'Validation failed', details: result.error.flatten().fieldErrors, }); } req.body = result.data; // replace raw input with parsed, typed data next(); };
} app.post('/users', validate(CreateUserSchema), createUserHandler);
import { z } from 'zod'; // Define schemas as your source of truth
const CreateUserSchema = z.object({ email: z.string().email('Invalid email format'), password: z .string() .min(12, 'Password must be at least 12 characters') .regex(/[A-Z]/, 'Must contain at least one uppercase letter') .regex(/[0-9]/, 'Must contain at least one number') .regex(/[^A-Za-z0-9]/, 'Must contain at least one special character'), name: z.string().min(2).max(100).trim(), role: z.enum(['user', 'admin']).default('user'), age: z.number().int().min(13).max(120).optional(),
}); // Middleware that validates and transforms
function validate(schema) { return (req, res, next) => { const result = schema.safeParse(req.body); if (!result.success) { return res.-weight: 500;">status(400).json({ error: 'Validation failed', details: result.error.flatten().fieldErrors, }); } req.body = result.data; // replace raw input with parsed, typed data next(); };
} app.post('/users', validate(CreateUserSchema), createUserHandler);
import { z } from 'zod'; // Define schemas as your source of truth
const CreateUserSchema = z.object({ email: z.string().email('Invalid email format'), password: z .string() .min(12, 'Password must be at least 12 characters') .regex(/[A-Z]/, 'Must contain at least one uppercase letter') .regex(/[0-9]/, 'Must contain at least one number') .regex(/[^A-Za-z0-9]/, 'Must contain at least one special character'), name: z.string().min(2).max(100).trim(), role: z.enum(['user', 'admin']).default('user'), age: z.number().int().min(13).max(120).optional(),
}); // Middleware that validates and transforms
function validate(schema) { return (req, res, next) => { const result = schema.safeParse(req.body); if (!result.success) { return res.-weight: 500;">status(400).json({ error: 'Validation failed', details: result.error.flatten().fieldErrors, }); } req.body = result.data; // replace raw input with parsed, typed data next(); };
} app.post('/users', validate(CreateUserSchema), createUserHandler);
const GetUserSchema = z.object({ id: z.string().uuid('Invalid user ID format'),
}); app.get('/users/:id', (req, res, next) => { const result = GetUserSchema.safeParse(req.params); if (!result.success) return res.-weight: 500;">status(400).json({ error: 'Invalid user ID' }); req.params = result.data; next();
}, getUserHandler);
const GetUserSchema = z.object({ id: z.string().uuid('Invalid user ID format'),
}); app.get('/users/:id', (req, res, next) => { const result = GetUserSchema.safeParse(req.params); if (!result.success) return res.-weight: 500;">status(400).json({ error: 'Invalid user ID' }); req.params = result.data; next();
}, getUserHandler);
const GetUserSchema = z.object({ id: z.string().uuid('Invalid user ID format'),
}); app.get('/users/:id', (req, res, next) => { const result = GetUserSchema.safeParse(req.params); if (!result.success) return res.-weight: 500;">status(400).json({ error: 'Invalid user ID' }); req.params = result.data; next();
}, getUserHandler);
// ✓ Load from environment
const dbPassword = process.env.DB_PASSWORD;
const jwtSecret = process.env.JWT_SECRET;
const apiKey = process.env.STRIPE_API_KEY; // ✗ Never hard-code
const dbPassword = 'supersecret123'; // NO
// ✓ Load from environment
const dbPassword = process.env.DB_PASSWORD;
const jwtSecret = process.env.JWT_SECRET;
const apiKey = process.env.STRIPE_API_KEY; // ✗ Never hard-code
const dbPassword = 'supersecret123'; // NO
// ✓ Load from environment
const dbPassword = process.env.DB_PASSWORD;
const jwtSecret = process.env.JWT_SECRET;
const apiKey = process.env.STRIPE_API_KEY; // ✗ Never hard-code
const dbPassword = 'supersecret123'; // NO
const REQUIRED_SECRETS = [ 'DATABASE_URL', 'JWT_SECRET', 'STRIPE_API_KEY', 'REDIS_URL',
]; function validateSecrets() { const missing = REQUIRED_SECRETS.filter(k => !process.env[k]); if (missing.length > 0) { console.error('FATAL: Missing required secrets:', missing.join(', ')); process.exit(1); // fail fast — don't -weight: 500;">start a broken app }
} validateSecrets(); // call before any other initialization
const REQUIRED_SECRETS = [ 'DATABASE_URL', 'JWT_SECRET', 'STRIPE_API_KEY', 'REDIS_URL',
]; function validateSecrets() { const missing = REQUIRED_SECRETS.filter(k => !process.env[k]); if (missing.length > 0) { console.error('FATAL: Missing required secrets:', missing.join(', ')); process.exit(1); // fail fast — don't -weight: 500;">start a broken app }
} validateSecrets(); // call before any other initialization
const REQUIRED_SECRETS = [ 'DATABASE_URL', 'JWT_SECRET', 'STRIPE_API_KEY', 'REDIS_URL',
]; function validateSecrets() { const missing = REQUIRED_SECRETS.filter(k => !process.env[k]); if (missing.length > 0) { console.error('FATAL: Missing required secrets:', missing.join(', ')); process.exit(1); // fail fast — don't -weight: 500;">start a broken app }
} validateSecrets(); // call before any other initialization
-weight: 500;">npm -weight: 500;">install --save-dev @axiom-experiment/env-sentinel
-weight: 500;">npm -weight: 500;">install --save-dev @axiom-experiment/env-sentinel
-weight: 500;">npm -weight: 500;">install --save-dev @axiom-experiment/env-sentinel
{ "scripts": { "precommit": "env-sentinel scan ." }
}
{ "scripts": { "precommit": "env-sentinel scan ." }
}
{ "scripts": { "precommit": "env-sentinel scan ." }
}
# Audit your current dependency tree
-weight: 500;">npm audit # Fix automatically-patchable vulnerabilities
-weight: 500;">npm audit fix # See the full report with details
-weight: 500;">npm audit --json | jq '.vulnerabilities | to_entries[] | select(.value.severity == "critical" or .value.severity == "high")'
# Audit your current dependency tree
-weight: 500;">npm audit # Fix automatically-patchable vulnerabilities
-weight: 500;">npm audit fix # See the full report with details
-weight: 500;">npm audit --json | jq '.vulnerabilities | to_entries[] | select(.value.severity == "critical" or .value.severity == "high")'
# Audit your current dependency tree
-weight: 500;">npm audit # Fix automatically-patchable vulnerabilities
-weight: 500;">npm audit fix # See the full report with details
-weight: 500;">npm audit --json | jq '.vulnerabilities | to_entries[] | select(.value.severity == "critical" or .value.severity == "high")'
# .github/workflows/security.yml
name: Security Audit on: push: branches: [main] pull_request: branches: [main] schedule: - cron: '0 9 * * 1' # weekly on Monday morning jobs: audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' - run: -weight: 500;">npm ci - run: -weight: 500;">npm audit --audit-level=high # Fail the build on high/critical vulnerabilities
# .github/workflows/security.yml
name: Security Audit on: push: branches: [main] pull_request: branches: [main] schedule: - cron: '0 9 * * 1' # weekly on Monday morning jobs: audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' - run: -weight: 500;">npm ci - run: -weight: 500;">npm audit --audit-level=high # Fail the build on high/critical vulnerabilities
# .github/workflows/security.yml
name: Security Audit on: push: branches: [main] pull_request: branches: [main] schedule: - cron: '0 9 * * 1' # weekly on Monday morning jobs: audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' - run: -weight: 500;">npm ci - run: -weight: 500;">npm audit --audit-level=high # Fail the build on high/critical vulnerabilities
# Always commit package-lock.json
# Never use --no-save or delete lock files
# Regularly -weight: 500;">update to get security patches
-weight: 500;">npm -weight: 500;">update
# Always commit package-lock.json
# Never use --no-save or delete lock files
# Regularly -weight: 500;">update to get security patches
-weight: 500;">npm -weight: 500;">update
# Always commit package-lock.json
# Never use --no-save or delete lock files
# Regularly -weight: 500;">update to get security patches
-weight: 500;">npm -weight: 500;">update
// ✗ VULNERABLE — string interpolation in SQL
const userId = req.params.id;
const result = await db.query(`SELECT * FROM users WHERE id = '${userId}'`);
// An attacker can pass: ' OR '1'='1 — returns all rows // ✓ SAFE — parameterized query
const result = await db.query('SELECT * FROM users WHERE id = $1', [userId]); // ✓ SAFE — ORM handles this automatically
const user = await prisma.user.findUnique({ where: { id: userId },
});
// ✗ VULNERABLE — string interpolation in SQL
const userId = req.params.id;
const result = await db.query(`SELECT * FROM users WHERE id = '${userId}'`);
// An attacker can pass: ' OR '1'='1 — returns all rows // ✓ SAFE — parameterized query
const result = await db.query('SELECT * FROM users WHERE id = $1', [userId]); // ✓ SAFE — ORM handles this automatically
const user = await prisma.user.findUnique({ where: { id: userId },
});
// ✗ VULNERABLE — string interpolation in SQL
const userId = req.params.id;
const result = await db.query(`SELECT * FROM users WHERE id = '${userId}'`);
// An attacker can pass: ' OR '1'='1 — returns all rows // ✓ SAFE — parameterized query
const result = await db.query('SELECT * FROM users WHERE id = $1', [userId]); // ✓ SAFE — ORM handles this automatically
const user = await prisma.user.findUnique({ where: { id: userId },
});
// PostgreSQL (pg library)
await pool.query('SELECT * FROM orders WHERE user_id = $1 AND -weight: 500;">status = $2', [userId, -weight: 500;">status]); // MySQL (mysql2 library)
await connection.execute('SELECT * FROM orders WHERE user_id = ? AND -weight: 500;">status = ?', [userId, -weight: 500;">status]); // SQLite (better-sqlite3)
const stmt = db.prepare('SELECT * FROM orders WHERE user_id = ? AND -weight: 500;">status = ?');
const rows = stmt.all(userId, -weight: 500;">status);
// PostgreSQL (pg library)
await pool.query('SELECT * FROM orders WHERE user_id = $1 AND -weight: 500;">status = $2', [userId, -weight: 500;">status]); // MySQL (mysql2 library)
await connection.execute('SELECT * FROM orders WHERE user_id = ? AND -weight: 500;">status = ?', [userId, -weight: 500;">status]); // SQLite (better-sqlite3)
const stmt = db.prepare('SELECT * FROM orders WHERE user_id = ? AND -weight: 500;">status = ?');
const rows = stmt.all(userId, -weight: 500;">status);
// PostgreSQL (pg library)
await pool.query('SELECT * FROM orders WHERE user_id = $1 AND -weight: 500;">status = $2', [userId, -weight: 500;">status]); // MySQL (mysql2 library)
await connection.execute('SELECT * FROM orders WHERE user_id = ? AND -weight: 500;">status = ?', [userId, -weight: 500;">status]); // SQLite (better-sqlite3)
const stmt = db.prepare('SELECT * FROM orders WHERE user_id = ? AND -weight: 500;">status = ?');
const rows = stmt.all(userId, -weight: 500;">status);
// Redirect all HTTP to HTTPS
app.use((req, res, next) => { if (req.headers['x-forwarded-proto'] !== 'https' && process.env.NODE_ENV === 'production') { return res.redirect(301, `https://${req.headers.host}${req.url}`); } next();
});
// Redirect all HTTP to HTTPS
app.use((req, res, next) => { if (req.headers['x-forwarded-proto'] !== 'https' && process.env.NODE_ENV === 'production') { return res.redirect(301, `https://${req.headers.host}${req.url}`); } next();
});
// Redirect all HTTP to HTTPS
app.use((req, res, next) => { if (req.headers['x-forwarded-proto'] !== 'https' && process.env.NODE_ENV === 'production') { return res.redirect(301, `https://${req.headers.host}${req.url}`); } next();
});
app.use( helmet.hsts({ maxAge: 31536000, // 1 year in seconds includeSubDomains: true, // apply to all subdomains preload: true, // submit to browser HSTS preload list })
);
app.use( helmet.hsts({ maxAge: 31536000, // 1 year in seconds includeSubDomains: true, // apply to all subdomains preload: true, // submit to browser HSTS preload list })
);
app.use( helmet.hsts({ maxAge: 31536000, // 1 year in seconds includeSubDomains: true, // apply to all subdomains preload: true, // submit to browser HSTS preload list })
);
import jwt from 'jsonwebtoken'; const JWT_SECRET = process.env.JWT_SECRET; // min 256 bits of entropy
const JWT_EXPIRY = '15m'; // short-lived tokens
const REFRESH_EXPIRY = '7d'; // separate refresh token function issueTokens(userId) { const accessToken = jwt.sign( { sub: userId, type: 'access' }, JWT_SECRET, { expiresIn: JWT_EXPIRY, algorithm: 'HS256' } ); const refreshToken = jwt.sign( { sub: userId, type: 'refresh' }, JWT_SECRET, { expiresIn: REFRESH_EXPIRY, algorithm: 'HS256' } ); return { accessToken, refreshToken };
} function verifyToken(token) { try { return jwt.verify(token, JWT_SECRET, { algorithms: ['HS256'] }); } catch (err) { return null; // expired, invalid signature, or malformed }
}
import jwt from 'jsonwebtoken'; const JWT_SECRET = process.env.JWT_SECRET; // min 256 bits of entropy
const JWT_EXPIRY = '15m'; // short-lived tokens
const REFRESH_EXPIRY = '7d'; // separate refresh token function issueTokens(userId) { const accessToken = jwt.sign( { sub: userId, type: 'access' }, JWT_SECRET, { expiresIn: JWT_EXPIRY, algorithm: 'HS256' } ); const refreshToken = jwt.sign( { sub: userId, type: 'refresh' }, JWT_SECRET, { expiresIn: REFRESH_EXPIRY, algorithm: 'HS256' } ); return { accessToken, refreshToken };
} function verifyToken(token) { try { return jwt.verify(token, JWT_SECRET, { algorithms: ['HS256'] }); } catch (err) { return null; // expired, invalid signature, or malformed }
}
import jwt from 'jsonwebtoken'; const JWT_SECRET = process.env.JWT_SECRET; // min 256 bits of entropy
const JWT_EXPIRY = '15m'; // short-lived tokens
const REFRESH_EXPIRY = '7d'; // separate refresh token function issueTokens(userId) { const accessToken = jwt.sign( { sub: userId, type: 'access' }, JWT_SECRET, { expiresIn: JWT_EXPIRY, algorithm: 'HS256' } ); const refreshToken = jwt.sign( { sub: userId, type: 'refresh' }, JWT_SECRET, { expiresIn: REFRESH_EXPIRY, algorithm: 'HS256' } ); return { accessToken, refreshToken };
} function verifyToken(token) { try { return jwt.verify(token, JWT_SECRET, { algorithms: ['HS256'] }); } catch (err) { return null; // expired, invalid signature, or malformed }
}
res.cookie('refreshToken', token, { httpOnly: true, // not accessible via document.cookie secure: true, // HTTPS only sameSite: 'strict', // CSRF protection maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days in milliseconds path: '/auth/refresh', // scope the cookie to the refresh endpoint only
});
res.cookie('refreshToken', token, { httpOnly: true, // not accessible via document.cookie secure: true, // HTTPS only sameSite: 'strict', // CSRF protection maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days in milliseconds path: '/auth/refresh', // scope the cookie to the refresh endpoint only
});
res.cookie('refreshToken', token, { httpOnly: true, // not accessible via document.cookie secure: true, // HTTPS only sameSite: 'strict', // CSRF protection maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days in milliseconds path: '/auth/refresh', // scope the cookie to the refresh endpoint only
});
function securityLog(event, req, details = {}) { console.log(JSON.stringify({ timestamp: new Date().toISOString(), type: 'security', event, ip: req.ip || req.socket.remoteAddress, userAgent: req.headers['user-agent'], path: req.path, method: req.method, userId: req.user?.id || null, ...details, }));
} // Log these events at minimum:
// - Failed login attempts
// - Rate limit hits
// - Validation failures with suspicious patterns
// - Unexpected 500 errors
// - Authentication token failures
// - Access to sensitive endpoints app.post('/auth/login', authLimiter, async (req, res) => { const { email, password } = req.body; const user = await findUserByEmail(email); if (!user || !(await verifyPassword(password, user.passwordHash))) { securityLog('login_failed', req, { email: email.slice(0, 5) + '***' }); return res.-weight: 500;">status(401).json({ error: 'Invalid credentials' }); } securityLog('login_success', req, { userId: user.id }); const tokens = issueTokens(user.id); res.json({ accessToken: tokens.accessToken });
});
function securityLog(event, req, details = {}) { console.log(JSON.stringify({ timestamp: new Date().toISOString(), type: 'security', event, ip: req.ip || req.socket.remoteAddress, userAgent: req.headers['user-agent'], path: req.path, method: req.method, userId: req.user?.id || null, ...details, }));
} // Log these events at minimum:
// - Failed login attempts
// - Rate limit hits
// - Validation failures with suspicious patterns
// - Unexpected 500 errors
// - Authentication token failures
// - Access to sensitive endpoints app.post('/auth/login', authLimiter, async (req, res) => { const { email, password } = req.body; const user = await findUserByEmail(email); if (!user || !(await verifyPassword(password, user.passwordHash))) { securityLog('login_failed', req, { email: email.slice(0, 5) + '***' }); return res.-weight: 500;">status(401).json({ error: 'Invalid credentials' }); } securityLog('login_success', req, { userId: user.id }); const tokens = issueTokens(user.id); res.json({ accessToken: tokens.accessToken });
});
function securityLog(event, req, details = {}) { console.log(JSON.stringify({ timestamp: new Date().toISOString(), type: 'security', event, ip: req.ip || req.socket.remoteAddress, userAgent: req.headers['user-agent'], path: req.path, method: req.method, userId: req.user?.id || null, ...details, }));
} // Log these events at minimum:
// - Failed login attempts
// - Rate limit hits
// - Validation failures with suspicious patterns
// - Unexpected 500 errors
// - Authentication token failures
// - Access to sensitive endpoints app.post('/auth/login', authLimiter, async (req, res) => { const { email, password } = req.body; const user = await findUserByEmail(email); if (!user || !(await verifyPassword(password, user.passwordHash))) { securityLog('login_failed', req, { email: email.slice(0, 5) + '***' }); return res.-weight: 500;">status(401).json({ error: 'Invalid credentials' }); } securityLog('login_success', req, { userId: user.id }); const tokens = issueTokens(user.id); res.json({ accessToken: tokens.accessToken });
}); - Content-Security-Policy — prevents XSS by restricting which scripts can execute
- X-Content-Type-Options: nosniff — stops browsers from MIME-sniffing responses
- X-Frame-Options: SAMEORIGIN — blocks clickjacking via iframes
- Strict-Transport-Security — enforces HTTPS on subsequent visits
- X-XSS-Protection — legacy XSS filter for older browsers
- Referrer-Policy — controls what referrer info is sent - AWS Secrets Manager / Parameter Store
- HashiCorp Vault
- Kubernetes Secrets (with external-secrets operator for rotation) - [ ] helmet() installed and configured before all other middleware
- [ ] Rate limiting on all endpoints; strict limits on auth endpoints
- [ ] CORS configured with explicit origin allowlist
- [ ] All request input validated with Zod or equivalent
- [ ] No secrets in source code or -weight: 500;">git history — use env vars + secrets manager
- [ ] -weight: 500;">npm audit passes with no critical/high vulnerabilities
- [ ] Parameterized queries everywhere SQL is used directly
- [ ] HTTPS enforced in production, HSTS enabled
- [ ] JWTs have short expiry, stored appropriately, algorithm explicitly specified
- [ ] Security events logged and shipped to aggregator
- [ ] Dependencies pinned via lock file and updated regularly
- [ ] Pre-commit hooks check for accidentally committed secrets (hookguard) - Run -weight: 500;">npm audit in the next 5 minutes
- Add helmet() if it's missing — 30 seconds of work
- Check your auth endpoints for rate limiting — if there's none, add it today
- Grep your codebase for process.env — are you failing fast on missing secrets at startup?