A masterclass on modern Web Application Security (AppSec), Same-Origin Policy (SOP), Content Security Policy (CSP v3), XSS/CSRF defense-in-depth, Argon2id cryptography, JWT hardening, SSRF prevention, and enterprise threat modeling.
- Stage 1: Absolute Beginner Foundations & The Browser Security Model
- Stage 2: OWASP Top 10 Vulnerabilities & Exploitation Vectors
- Stage 3: Cross-Site Scripting (XSS) & Content Security Policy (CSP v3)
- Stage 4: CSRF Defense, Clickjacking & Cookie Prefix Architecture
- Stage 5: Cryptography, Argon2id & JWT Threat Mitigation
- Stage 6: SSRF Prevention, Network Segmentation & Hardened Headers
- Stage 7: Staff Security Architect Interview Handbook & Production Cheatsheet
graph TD
subgraph "Legacy Perimeter Security"
WAF["Web Application Firewall (WAF)"] -->|"Single Barrier"| APP["Monolithic Web App"]
APP -->|"Plaintext Secrets & Unchecked Queries"| DB[("Database")]
end
subgraph "Modern Zero-Trust Defense-in-Depth"
CLIENT["Client Browser (CSP v3 + Trusted Types)"] --> WAF2["Edge WAF & DDoS Shield"]
WAF2 --> APP2["Hardened Application (Zod Validation + Rate Limit)"]
APP2 --> M2M["mTLS & Scoped RBAC Tokens"]
M2M --> DB2[("Encrypted DB with Parameterized Queries")]
end
The foundational security pillar of modern web browsers: scripts running on one origin cannot read or mutate data from another origin unless explicitly granted permission via Cross-Origin Resource Sharing (CORS).
An origin is defined strictly as the triplet: Scheme + Host + Port.
graph TD
BASE["Origin: https://app.example.com:443"]
BASE -->|"Same Scheme, Same Host, Same Port"| MATCH["https://app.example.com/api/profile (SAME ORIGIN)"]
BASE -->|"Different Subdomain"| FAIL1["https://api.example.com (CROSS ORIGIN)"]
BASE -->|"Different Scheme"| FAIL2["http://app.example.com (CROSS ORIGIN)"]
BASE -->|"Different Port"| FAIL3["https://app.example.com:8443 (CROSS ORIGIN)"]
- A01: Broken Access Control (BOLA / IDOR): Flaws allowing users to access resources belonging to other users simply by changing an ID parameter (
GET /api/documents/doc-9921). - A02: Cryptographic Failures: Using deprecated hashing algorithms (MD5, SHA-1), storing plaintext credentials, or weak TLS configurations.
- A03: Injection (SQL, Command, LDAP): Concatenating untrusted user input directly into interpreter execution strings.
- A04: Insecure Design: Flaws in business logic (e.g. lack of rate limiting on password reset flows, permitting brute-force enumeration).
- A05: Security Misconfiguration: Unnecessary features enabled, default accounts/passwords, overly permissive CORS (
Access-Control-Allow-Origin: *with credentials), verbose stack traces in production. - A10: Server-Side Request Forgery (SSRF): Flaws allowing attackers to induce the backend server to make unauthorized HTTP requests to internal VPC resources (e.g., querying AWS IMDS
http://169.254.169.254/latest/meta-data/).
- Stored (Persistent) XSS: Malicious script is saved in the database (e.g., in a comment or profile bio) and served to subsequent visitors.
- Reflected XSS: Malicious script is included in a URL parameter or form input and immediately echoed back in the response page.
- DOM-Based XSS: Malicious script executes entirely on the client side via unsafe DOM manipulation (
innerHTML,document.write,eval()).
graph LR
ATTACKER["Attacker"] -->|"Injects <script>alert(1)</script>"| SERVER[("Database / Web Server")]
SERVER -->|"Serves Unsanitized Markup"| VICTIM["Victim Browser"]
VICTIM -->|"Script Executes in Victim Context"| EXFIL["Exfiltrates Session Cookies / Tokens"]
A strict Content Security Policy eliminates 99% of XSS vectors by disallowing inline scripts unless they carry a cryptographically secure, per-request random nonce:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-rAnd0m12345' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; require-trusted-types-for 'script';CSRF tricks an authenticated user's browser into executing an unwanted action on a trusted application:
graph TD
USER["User logged into bank.com (Session Cookie in Browser)"]
EVIL["User visits malicious-site.com"]
EVIL -->|"Sends hidden auto-submitting form: POST bank.com/transfer"| BANK["bank.com Server"]
BANK -->|"Browser automatically attaches bank.com cookie!"| LOSS["Unauthorized Transfer Executed!"]
Defense: SameSite=Strict and Synchronizer Token Pattern.
Hardened cookie prefixes enforced by modern browsers:
__Secure-: Cookie must be sent over HTTPS (Secureflag required).__Host-: Cookie must be sent over HTTPS, must havepath=/, and cannot have aDomainattribute set (prevents subdomain shadowing attacks).
Set-Cookie: __Host-Session=s%3A99d8a1.f9d8; Path=/; Secure; HttpOnly; SameSite=Strict; Max-Age=86400Argon2id (winner of the Password Hashing Competition) provides memory-hard and time-hard resistance against GPU and ASIC cracking:
// src/services/crypto.service.ts
import argon2 from 'argon2';
export async function hashPassword(plainTextPassword: string): Promise<string> {
return await argon2.hash(plainTextPassword, {
type: argon2.argon2id,
memoryCost: 65536, // 64 MiB RAM requirement
timeCost: 3, // 3 iterations
parallelism: 4, // 4 parallel threads
});
}
export async function verifyPassword(storedHash: string, candidatePassword: string): Promise<boolean> {
return await argon2.verify(storedHash, candidatePassword);
}- Reject
alg: none: Always explicitly specify allowed algorithms (algorithms: ['RS256']). - Asymmetric Signing (RS256/ES256): Sign tokens with a private RSA key; verify tokens with a public key. This prevents services that only need verification from minting forged tokens.
- Short Lifetime + Refresh Token Rotation: Access token expires in 15 minutes; Refresh token stored in
__Host-cookie with database revocation list.
When fetching user-provided URLs (e.g. webhooks, link previews), attackers can target internal cloud metadata endpoints (http://169.254.169.254) or internal IP ranges (10.0.0.0/8, 127.0.0.1, 192.168.0.0/16).
// src/services/safeFetch.service.ts
import dns from 'dns/promises';
import net from 'net';
const PRIVATE_IP_RANGES = [
/^127\./, // Loopback
/^10\./, // Class A private
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // Class B private
/^192\.168\./, // Class C private
/^169\.254\./, // AWS Link-local / Cloud Metadata
/^::1$/, // IPv6 loopback
/^fc00:/, // IPv6 private
];
export async function validateAndFetchUrl(targetUrl: string) {
const parsed = new URL(targetUrl);
// 1. Enforce HTTPS only
if (parsed.protocol !== 'https:') {
throw new Error('SSRF Guard: Only HTTPS requests are permitted.');
}
// 2. Resolve DNS IP addresses before fetching (Prevents DNS Rebinding)
const addresses = await dns.resolve4(parsed.hostname);
for (const ip of addresses) {
if (PRIVATE_IP_RANGES.some((regex) => regex.test(ip))) {
throw new Error(`SSRF Guard: Target IP "${ip}" belongs to a protected internal network range.`);
}
}
// 3. Safe fetch with timeout and redirection limit
return await fetch(targetUrl, {
redirect: 'error', // Disallow redirects to prevent DNS rebinding hops
signal: AbortSignal.timeout(5000),
});
}Generates a unique cryptographically random nonce for every HTTP request, allowing inline scripts safely without 'unsafe-inline':
// src/middleware/securityHeaders.ts
import { Request, Response, NextFunction } from 'express';
import helmet from 'helmet';
import crypto from 'crypto';
export function securityHeaders() {
return (req: Request, res: Response, next: NextFunction) => {
// 1. Generate 128-bit cryptographically secure random nonce
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.cspNonce = nonce;
// 2. Configure Helmet with strict Content-Security-Policy v3
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: [
"'self'",
`'nonce-${nonce}'`,
"'strict-dynamic'",
// Fallback for older browsers
'https:',
],
styleSrc: ["'self'", `'nonce-${nonce}'`],
imgSrc: ["'self'", 'data:', 'https://images.unsplash.com'],
objectSrc: ["'none'"],
baseUri: ["'none'"],
formAction: ["'self'"],
frameAncestors: ["'none'"], // Complete Clickjacking prevention
upgradeInsecureRequests: [],
},
},
crossOriginEmbedderPolicy: true,
crossOriginOpenerPolicy: { policy: 'same-origin' },
crossOriginResourcePolicy: { policy: 'same-origin' },
dnsPrefetchControl: { allow: false },
frameguard: { action: 'deny' },
hidePoweredBy: true, // Strips X-Powered-By: Express
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
ieNoOpen: true,
noSniff: true,
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
xssFilter: true,
})(req, res, next);
};
}
export function generateAuthenticationOptions(rpId: string): { challenge: string; rpId: string; timeout: number } {
return {
challenge: crypto.randomBytes(32).toString('base64url'),
rpId,
timeout: 60000,
};
}
export function verifyPasskeySignature(clientDataJSON: string, authenticatorData: Buffer, signature: Buffer, publicKeyPem: string): boolean {
const clientDataHash = crypto.createHash('sha256').update(clientDataJSON).digest();
const signatureBase = Buffer.concat([authenticatorData, clientDataHash]);
const verifier = crypto.createVerify('SHA256');
verifier.update(signatureBase);
return verifier.verify(publicKeyPem, signature);
}Validates that incoming mutation requests contain a matching CSRF token signed with an HMAC secret:
// src/middleware/csrfProtection.ts
import { Request, Response, NextFunction } from 'express';
import crypto from 'crypto';
const CSRF_SECRET = process.env.CSRF_SECRET || 'super-secret-csrf-signing-key-32b';
export function generateCsrfToken(sessionId: string): string {
const timestamp = Date.now().toString();
const salt = crypto.randomBytes(8).toString('hex');
const signature = crypto
.createHmac('sha256', CSRF_SECRET)
.update(`${sessionId}.${timestamp}.${salt}`)
.digest('hex');
return `${timestamp}.${salt}.${signature}`;
}
export function verifyCsrfToken(sessionId: string, token: string, maxAgeMs: number = 3600000): boolean {
const parts = token.split('.');
if (parts.length !== 3) return false;
const [timestampStr, salt, providedSig] = parts;
const timestamp = parseInt(timestampStr, 10);
// Check token age (1 hour limit)
if (Date.now() - timestamp > maxAgeMs) {
return false;
}
// Verify HMAC signature using constant-time comparison
const expectedSig = crypto
.createHmac('sha256', CSRF_SECRET)
.update(`${sessionId}.${timestampStr}.${salt}`)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(providedSig), Buffer.from(expectedSig));
}
export function csrfMiddleware() {
return (req: Request, res: Response, next: NextFunction) => {
// Safe HTTP methods do not require CSRF tokens
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
return next();
}
const sessionId = req.cookies?.['__Host-SessionId'];
const submittedToken = (req.headers['x-csrf-token'] as string) || req.body?._csrf;
if (!sessionId || !submittedToken || !verifyCsrfToken(sessionId, submittedToken)) {
return res.status(403).json({
type: 'https://api.example.com/errors/csrf-validation-failed',
title: 'Forbidden',
status: 403,
detail: 'Invalid or missing CSRF authenticity token.',
instance: req.originalUrl,
});
}
next();
};
}
export function generateAuthenticationOptions(rpId: string): { challenge: string; rpId: string; timeout: number } {
return {
challenge: crypto.randomBytes(32).toString('base64url'),
rpId,
timeout: 60000,
};
}
export function verifyPasskeySignature(clientDataJSON: string, authenticatorData: Buffer, signature: Buffer, publicKeyPem: string): boolean {
const clientDataHash = crypto.createHash('sha256').update(clientDataJSON).digest();
const signatureBase = Buffer.concat([authenticatorData, clientDataHash]);
const verifier = crypto.createVerify('SHA256');
verifier.update(signatureBase);
return verifier.verify(publicKeyPem, signature);
}Mitigates token theft through short-lived access tokens and single-use refresh token rotation:
// src/services/auth.service.ts
import jwt from 'jsonwebtoken';
import crypto from 'crypto';
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET!;
const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!;
export interface UserSessionPayload {
userId: string;
role: string;
tokenFamilyId: string; // Unique ID to track family of rotated tokens
}
export async function issueTokens(userId: string, role: string, existingFamilyId?: string) {
const tokenFamilyId = existingFamilyId || crypto.randomUUID();
// 1. Short-lived Access Token (15 minutes)
const accessToken = jwt.sign(
{ userId, role, tokenFamilyId },
ACCESS_SECRET,
{ expiresIn: '15m', algorithm: 'HS256' }
);
// 2. Single-use Refresh Token (7 days)
const refreshToken = jwt.sign(
{ userId, role, tokenFamilyId, jti: crypto.randomUUID() },
REFRESH_SECRET,
{ expiresIn: '7d', algorithm: 'HS256' }
);
// Store active family in Redis (valid for 7 days)
await redis.set(`auth:family:${tokenFamilyId}`, userId, 'EX', 7 * 86400);
return { accessToken, refreshToken, tokenFamilyId };
}
export async function rotateRefreshToken(oldRefreshToken: string) {
try {
const payload = jwt.verify(oldRefreshToken, REFRESH_SECRET) as any;
const { userId, role, tokenFamilyId, jti } = payload;
// Detect Replay Attack: Has this refresh token JTI been used before?
const isBlacklisted = await redis.get(`auth:blacklist:${jti}`);
if (isBlacklisted) {
// BREACH DETECTED: Someone attempted to reuse a spent token!
// Invalidate the entire token family immediately!
await redis.del(`auth:family:${tokenFamilyId}`);
throw new Error('Token reuse detected. All sessions in this family have been revoked.');
}
// Blacklist the used refresh token immediately
await redis.set(`auth:blacklist:${jti}`, 'used', 'EX', 7 * 86400);
// Verify token family is still valid
const activeUser = await redis.get(`auth:family:${tokenFamilyId}`);
if (!activeUser) {
throw new Error('Session has been revoked.');
}
// Issue brand new token pair within same family
return await issueTokens(userId, role, tokenFamilyId);
} catch (err: any) {
throw new Error(`Authentication failure: ${err.message}`);
}
}Prevents XSS in rich-text CMS inputs using JSDOM and DOMPurify:
// src/utils/sanitizer.ts
import createDOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window as any);
// Configure strict DOMPurify whitelist
export function sanitizeRichText(dirtyHtml: string): string {
return DOMPurify.sanitize(dirtyHtml, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'ol', 'li', 'code', 'pre'],
ALLOWED_ATTR: ['href', 'target', 'rel'],
// Force rel="noopener noreferrer" on external links
ADD_ATTR: ['target'],
FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed', 'form'],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover'],
});
}
// Plain text encoder for safe HTML interpolation
export function escapeHtml(unsafeString: string): string {
return unsafeString
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}Slows down brute-force credential stuffing with progressive delays and temporary lockout:
// src/services/loginRateLimit.service.ts
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
export async function registerFailedLoginAttempt(accountEmail: string): Promise<{ isLocked: boolean; remainingAttempts: number; lockoutSeconds?: number }> {
const key = `failed_login:${accountEmail}`;
const attempts = await redis.incr(key);
if (attempts === 1) {
await redis.expire(key, 900); // 15 minute observation window
}
if (attempts >= 5) {
const lockoutDuration = 900; // 15 minute lockout
await redis.set(`lockout:${accountEmail}`, 'locked', 'EX', lockoutDuration);
return { isLocked: true, remainingAttempts: 0, lockoutSeconds: lockoutDuration };
}
return { isLocked: false, remainingAttempts: 5 - attempts };
}
export async function isAccountLocked(accountEmail: string): Promise<boolean> {
const locked = await redis.get(`lockout:${accountEmail}`);
return locked !== null;
}
export async function clearFailedLogins(accountEmail: string) {
await redis.del(`failed_login:${accountEmail}`);
await redis.del(`lockout:${accountEmail}`);
}Prevents accidental leakage of social security numbers, credit cards, and API secrets into centralized log aggregators:
// src/utils/piiMasker.ts
export function maskSensitiveData(payload: Record<string, any>): Record<string, any> {
const SENSITIVE_KEY_PATTERNS = [
/password/i,
/secret/i,
/token/i,
/apiKey/i,
/auth/i,
/creditCard/i,
/cvv/i,
/ssn/i,
];
const CARD_REGEX = /�(?:\d[ -]*?){13,16}�/g;
const EMAIL_REGEX = /([a-zA-Z0-9_\.-]+)@([a-zA-Z0-9_\.-]+\.[a-zA-Z]{2,6})/g;
function redactValue(val: any): any {
if (typeof val === 'string') {
let redacted = val.replace(CARD_REGEX, '****-****-****-****');
redacted = redacted.replace(EMAIL_REGEX, '$1***@$2');
return redacted;
}
if (typeof val === 'object' && val !== null) {
const cloned: Record<string, any> = Array.isArray(val) ? [] : {};
for (const [k, v] of Object.entries(val)) {
if (SENSITIVE_KEY_PATTERNS.some((rx) => rx.test(k))) {
cloned[k] = '[REDACTED]';
} else {
cloned[k] = redactValue(v);
}
}
return cloned;
}
return val;
}
return redactValue(payload);
}Enforces bidirectional X.509 certificate authentication between microservices:
// src/server/mtlsServer.ts
import https from 'https';
import fs from 'fs';
import { app } from './app';
const mtlsOptions: https.ServerOptions = {
// Server Identity
key: fs.readFileSync('/etc/ssl/server.key'),
cert: fs.readFileSync('/etc/ssl/server.crt'),
// Trusted Internal Certificate Authority (CA)
ca: fs.readFileSync('/etc/ssl/internal-ca.crt'),
// Mutual TLS: Enforce client certificate validation
requestCert: true,
rejectUnauthorized: true, // Rejects connection immediately if client certificate is untrusted
// Enforce TLS 1.3 only
minVersion: 'TLSv1.3',
};
const server = https.createServer(mtlsOptions, app);
server.listen(8443, () => {
console.log('Secure mTLS Microservice listening on port 8443');
});-- VULNERABLE CODE: String Concatenation
-- Input: admin' OR '1'='1' --
SELECT * FROM users WHERE email = 'admin' OR '1'='1' --' AND password = '...';
-- Result: AST modified! The condition '1'='1' evaluates true for all rows; attacker logs in as admin without password!
-- DEFENSE: Prepared Statement / Parameterized Query
SELECT * FROM users WHERE email = $1 AND password = $2;
-- Database engine treats $1 strictly as a literal string value, never altering the parser syntax tree.- Never store plaintext secrets in repositories: Use git hooks to scan for high-entropy tokens with tools like TruffleHog or Gitleaks.
- Rotate Database and API Credentials regularly: Use AWS Secrets Manager or HashiCorp Vault with automated 30-day rotation.
- Audit Third-Party NPM Dependencies in CI: Enforce
npm audit --audit-level=highor Snyk scans on every pull request. - Enforce Least-Privilege IAM Roles: Application servers should only have permission to execute specific database stored procedures or tables, not
DROP TABLEorGRANT. - Run Penetration Tests against OWASP Top 10: Schedule bi-annual black-box and white-box penetration tests by independent certified CREST assessors.
Tracks and limits active sessions per user account, allowing remote session revocation:
// src/services/session.service.ts
import Redis from 'ioredis';
import crypto from 'crypto';
const redis = new Redis(process.env.REDIS_URL!);
export interface SessionData {
sessionId: string;
userId: string;
ipAddress: string;
userAgent: string;
createdAt: string;
lastActiveAt: string;
}
export async function createSession(userId: string, ipAddress: string, userAgent: string, maxConcurrentSessions: number = 5): Promise<string> {
const sessionId = crypto.randomBytes(32).toString('hex');
const userSessionsKey = `user_sessions:${userId}`;
const sessionKey = `session:${sessionId}`;
const sessionData: SessionData = {
sessionId,
userId,
ipAddress,
userAgent,
createdAt: new Date().toISOString(),
lastActiveAt: new Date().toISOString(),
};
// 1. Store session payload with 24-hour expiration
await redis.set(sessionKey, JSON.stringify(sessionData), 'EX', 86400);
// 2. Add to user active session set
await redis.zadd(userSessionsKey, Date.now(), sessionId);
// 3. Enforce maximum concurrent active sessions (evict oldest)
const totalSessions = await redis.zcard(userSessionsKey);
if (totalSessions > maxConcurrentSessions) {
const oldestSessions = await redis.zrange(userSessionsKey, 0, totalSessions - maxConcurrentSessions - 1);
for (const oldId of oldestSessions) {
await redis.del(`session:${oldId}`);
await redis.zrem(userSessionsKey, oldId);
}
}
return sessionId;
}
export async function validateSession(sessionId: string): Promise<SessionData | null> {
const raw = await redis.get(`session:${sessionId}`);
if (!raw) return null;
const data: SessionData = JSON.parse(raw);
// Slide session window (extend TTL on activity)
await redis.expire(`session:${sessionId}`, 86400);
return data;
}
export async function revokeAllUserSessions(userId: string) {
const userSessionsKey = `user_sessions:${userId}`;
const sessionIds = await redis.zrange(userSessionsKey, 0, -1);
for (const id of sessionIds) {
await redis.del(`session:${id}`);
}
await redis.del(userSessionsKey);
}| Algorithm | Primary Use Case | Work Factor Type | Vulnerabilities / Status |
|---|---|---|---|
| Argon2id | User Password Hashing | Memory-Hard & Time-Hard | Gold Standard (RFC 9106, OWASP Preferred) |
| bcrypt | Password Storage (Legacy) | Time-Hard (CPU intensive) | Susceptible to modern FPGA/ASIC acceleration |
| PBKDF2 | FIPS-140 Compliance | Iteration Count | Vulnerable to GPU parallelization; requires >= 600,000 rounds |
| SHA-256 / SHA-3 | Digital Signatures & Data Integrity | Fast / Non-Iterative | Never use for passwords (computed at billions/sec on GPUs) |
| MD5 / SHA-1 | Checksums (Legacy) | Obsolete | Cryptographically broken; collision attacks trivial |
When an active data breach or credential leak is suspected:
- Rotate All Master Secrets Immediately: Issue emergency rotation of database master passwords, JWT signing keys, and external API tokens.
- Revoke Active User Sessions Globally: Flush active session registries (
revokeAllUserSessions) and invalidate refresh token families. - Inspect Gateway Access Logs for Lateral Movement: Grep reverse proxy access logs for correlated
X-Request-IDand source IP addresses. - Isolate Affected Pods and Take Memory Snapshots: Remove breached container instances from Kubernetes Service endpoints without destroying the pods to preserve volatile RAM evidence for forensic analysis.
Protects Single Page Applications (SPAs) and mobile native apps against authorization code interception attacks:
// src/utils/pkce.ts
import crypto from 'crypto';
// 1. Base64URL encoding helper
function base64UrlEncode(buffer: Buffer): string {
return buffer
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
// 2. Generate cryptographically random code verifier (43-128 characters)
export function generateCodeVerifier(): string {
return base64UrlEncode(crypto.randomBytes(32));
}
// 3. Compute code challenge from verifier using SHA-256
export function generateCodeChallenge(verifier: string): string {
const hash = crypto.createHash('sha256').update(verifier).digest();
return base64UrlEncode(hash);
}
// 4. Verify code verifier against stored challenge on token exchange
export function verifyCodeChallenge(verifier: string, storedChallenge: string): boolean {
const calculatedChallenge = generateCodeChallenge(verifier);
return crypto.timingSafeEqual(
Buffer.from(calculatedChallenge),
Buffer.from(storedChallenge)
);
}| OWASP Risk Category | Primary Vector | Prescribed Defensive Architecture |
|---|---|---|
| A01: Broken Access Control | IDOR / BOLA parameter tampering | Row-Level Security (RLS) & context-scoped DB queries |
| A02: Cryptographic Failures | Plaintext secrets & weak ciphers | Argon2id for passwords; AES-256-GCM; TLS 1.3 only |
| A03: Injection | SQL / Command string interpolation | Prepared statements with AST parameterization |
| A04: Insecure Design | Missing business limits & brute-force | Sliding-window rate limiting; multi-factor authentication |
| A05: Security Misconfiguration | Verbose headers & open ports | Helmet headers; noSniff; disabled server banners |
| A06: Vulnerable Components | Stale NPM / container CVEs | Automated daily Dependabot & Snyk vulnerability scans |
| A07: Identification & Auth | Credential stuffing & session hijack | Passkeys / WebAuthn; __Host- cookies; rotation |
| A08: Software & Data Integrity | Untrusted CI/CD code & tampered CDN | Subresource Integrity (SRI); signed commits |
| A09: Logging & Monitoring | Silent compromise without alerts | Audit trail logs; centralized SIEM; Prometheus alerts |
| A10: SSRF | Metadata scraping (169.254.169.254) |
Pre-resolve DNS; drop private CIDRs (10.0.0.0/8) |
Maintained by Manthan Ank. Compliant with NIST SP 800-63B (Digital Identity Guidelines) and OWASP Application Security Verification Standard (ASVS) Level 3.
Eliminates password phishing completely through hardware-bound cryptographic key pairs (Touch ID, Face ID, YubiKey):
// src/services/webauthn.service.ts
import crypto from 'crypto';
export interface RegistrationOptions {
challenge: string;
rp: { name: string; id: string };
user: { id: string; name: string; displayName: string };
pubKeyCredParams: Array<{ alg: number; type: 'public-key' }>;
authenticatorSelection: {
authenticatorAttachment?: 'platform' | 'cross-platform';
userVerification: 'required' | 'preferred';
residentKey: 'required';
};
timeout: number;
}
export function generateRegistrationOptions(userId: string, userEmail: string, rpId: string): RegistrationOptions {
// Generate 256-bit cryptographically secure challenge
const challenge = crypto.randomBytes(32).toString('base64url');
return {
challenge,
rp: {
name: 'HubScale Enterprise Security',
id: rpId, // e.g. 'hubscale.io'
},
user: {
id: Buffer.from(userId).toString('base64url'),
name: userEmail,
displayName: userEmail.split('@')[0],
},
pubKeyCredParams: [
{ alg: -7, type: 'public-key' }, // ES256 (ECDSA w/ SHA-256)
{ alg: -257, type: 'public-key' }, // RS256 (RSASSA-PKCS1-v1_5 w/ SHA-256)
],
authenticatorSelection: {
authenticatorAttachment: 'platform',
userVerification: 'required',
residentKey: 'required',
},
timeout: 60000,
};
}
export function generateAuthenticationOptions(rpId: string): { challenge: string; rpId: string; timeout: number } {
return {
challenge: crypto.randomBytes(32).toString('base64url'),
rpId,
timeout: 60000,
};
}
export function verifyPasskeySignature(clientDataJSON: string, authenticatorData: Buffer, signature: Buffer, publicKeyPem: string): boolean {
const clientDataHash = crypto.createHash('sha256').update(clientDataJSON).digest();
const signatureBase = Buffer.concat([authenticatorData, clientDataHash]);
const verifier = crypto.createVerify('SHA256');
verifier.update(signatureBase);
return verifier.verify(publicKeyPem, signature);
}Answer: The Same-Origin Policy prevents a script loaded from Origin A from reading confidential response data returned by Origin B. Historically, HTML allowed cross-origin writes via <form method="POST"> and <img> tags before SOP was formalized. While browsers prevent Origin A from inspecting the response of a cross-origin request, the request is still dispatched to the network and executed on the target server. This fundamental asymmetry is the exact vulnerability exploited by CSRF.
Answer: Stored XSS occurs when malicious input is persisted permanently in database storage and served to subsequent users inside server-rendered HTML. Reflected XSS occurs when input from a URL or form is immediately mirrored into the HTTP response page without sanitization. DOM-based XSS occurs entirely on the client side: the server sends clean HTML, but client-side JavaScript reads from an untrusted source (location.search, document.referrer) and passes it to an execution sink (element.innerHTML, eval()).
Answer: In CSP v3, 'strict-dynamic' combined with a cryptographically secure random nonce ('nonce-XYZ') allows any script that carries the valid nonce to dynamically create and append new child <script> elements to the DOM. Those dynamically generated child scripts are automatically trusted and executed by the browser without needing separate individual nonces or maintaining fragile domain whitelists.
Answer:
Strict: The cookie is never sent in cross-site requests, even when following a top-level link from an external website (e.g. clicking a link to your dashboard from an email).Lax: The cookie is withheld on cross-site sub-requests (images, iframes, cross-origin POST requests), but is sent when navigating via top-level safe GET requests (clicking an external link).None: The cookie is sent on all cross-site requests, requiring theSecureflag (Secure; SameSite=None).
Answer: BOLA (formerly IDOR - Insecure Direct Object References) occurs when an endpoint accepts a resource identifier (/api/documents/1024) and retrieves the resource from the database without validating that the authenticated session user actually owns or is authorized to view that specific record. Prevention: Enforce authorization checks in every database query (e.g. WHERE id = :documentId AND organization_id = :sessionOrgId) or utilize Row-Level Security (RLS) in the database engine.
Answer: Bcrypt and PBKDF2 are computationally intensive (time-hard) but require virtually zero memory. Attackers can build massive parallelized FPGA and ASIC cracking rigs that compute billions of bcrypt hashes per second. Argon2id is a memory-hard algorithm requiring dozens of megabytes of RAM per hash evaluation, making ASIC and GPU parallelization financially and physically prohibitive for attackers.
Answer: The JSON Web Token specification allows an algorithm header value of "alg": "none", designed for unauthenticated testing. Vulnerable verification libraries that did not enforce explicit algorithm whitelists accepted modified tokens where the signature was stripped and alg was set to "none", allowing attackers to forge administrative claims ({ "role": "admin" }) that were treated as valid.
Answer: An attacker registers a domain whose DNS server returns a public IP with a TTL of 1 second on the initial resolution check (passing the backend validation filter). A fraction of a second later, when the backend actually executes the HTTP request, the DNS server responds with 127.0.0.1 or 169.254.169.254. Prevention: Resolve the IP address once, validate it, and connect directly to the resolved IP address over a pinned socket or disable DNS lookups during request execution.
Answer: Clickjacking tricks a user into clicking invisible or disguised UI elements by loading the vulnerable application inside a transparent <iframe> overlaid on an enticing game or button. Prevention: Send the Content-Security-Policy: frame-ancestors 'none'; header (or legacy X-Frame-Options: DENY), which instructs browsers to block framing of the page entirely.
Answer: Browser-enforced security flags:
__Secure-: Rejects cookie creation unless theSecureflag is present (HTTPS required).__Host-: Rejects cookie creation unlessSecureis present,Path=/is specified, and theDomainattribute is omitted (preventing malicious subdomains from overwriting parent cookies).
Answer: Without nosniff, browsers attempt to inspect (sniff) file payloads. If a user uploads an avatar image containing <script>alert(1)</script>, the browser might execute it as HTML. X-Content-Type-Options: nosniff forces the browser to respect the declared Content-Type strictly.
Answer: Sends Strict-Transport-Security: max-age=31536000; includeSubDomains; preload. The browser caches this directive and automatically upgrades all subsequent http:// requests to https:// internally before sending packets over the network, rendering man-in-the-middle SSL stripping attacks impossible.
Answer: A security feature allowing browsers to verify that resources fetched from CDNs (scripts, stylesheets) have not been modified or compromised:
<script src="https://cdn.example.com/lib.js" integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC" crossorigin="anonymous"></script>.
Answer: Authentication verifies who the entity is (identity verification via password, MFA, passkey). Authorization determines what permissions and actions the authenticated entity is permitted to execute (RBAC, ABAC).
Answer: The server generates a unique, unpredictable cryptographically random token tied to the user's current session and embeds it in HTML forms or sends it via custom header. When the form is submitted, the server compares the submitted token against the session token; cross-site requests cannot read or forge this token due to SOP.
Answer: Standard string comparison (str1 === str2) returns false on the very first mismatched character, taking slightly less time for wrong characters than right ones. Attackers measure execution latency variations down to nanoseconds to guess secret tokens character-by-character. Prevention: Use constant-time comparison algorithms like crypto.timingSafeEqual().
Answer: Controls which browser hardware features and APIs (camera, microphone, geolocation, payment request) can be used by the document or embedded iframes:
Permissions-Policy: camera=(), microphone=(), geolocation=().
Answer: Concatenating unsanitized user input into database query strings alters the AST grammar of the query. While ORMs parameterize standard queries, developers can still introduce SQL injection vulnerabilities if they use raw query methods (prisma.$queryRawUnsafe, db.execute(sql"...") with string concatenation) with untrusted input.
Answer: An endpoint that accepts a redirect URL parameter (/login?redirect=https://evil.com) and redirects the user without validating that the target belongs to a trusted domain whitelist, enabling phishing attacks using trusted brand domains.
Answer: In response to an OPTIONS preflight request, the server includes Access-Control-Max-Age: 86400, instructing the browser to cache the CORS permission grant for 24 hours, avoiding extra network round-trips for subsequent requests.
Answer: An attacker accesses arbitrary files on the filesystem by manipulating input paths using ../ sequences (e.g., GET /download?file=../../../../etc/passwd). Prevention: Resolve paths with path.resolve(), verify they reside within the root directory, and validate against a strict alphanumeric whitelist.
Answer: Controls how much referrer information (URL path and query parameters) is included in the Referer header when navigating between pages or requesting external assets. Modern standard: Referrer-Policy: strict-origin-when-cross-origin.
Answer: Symmetric encryption (e.g. AES-256-GCM) uses the identical secret key for both encryption and decryption. Asymmetric encryption (e.g. RSA-4096, Ed25519) uses a mathematically linked key pair: a public key for encryption/signature verification and a private key for decryption/signing.
Answer: Occurs when user input is concatenated directly into template engine templates (Jinja2, Handlebars, EJS) instead of passed as data variables, allowing attackers to execute arbitrary code on the server via template syntax directives.
Answer: An attacker intercepts a legitimate request over the wire and repeats it to re-execute an action (e.g. duplicate payment). Mitigation: Include a timestamp and unique nonce in request signatures, rejecting requests with expired timestamps or repeated nonces.
Answer: Eliminates authorization code interception attacks on public clients (mobile apps, Single Page Applications) where client secrets cannot be stored securely, replacing secrets with a dynamically generated code_verifier and hashed code_challenge.
Answer: Sending duplicate query parameters (?role=user&role=admin). Depending on how backend frameworks parse arrays vs strings, authorization filters might check the first parameter while database operations execute using the second.
Answer: Automated injection of stolen username/password pairs from third-party data breaches against your login portal. Defense: Multi-Factor Authentication (MFA), CAPTCHA challenges on velocity thresholds, rate limiting by IP/account, and HaveIBeenPwned API breach checks on password creation.
Answer: A modern browser security feature enforced via CSP (require-trusted-types-for 'script') that locks down dangerous DOM execution sinks (innerHTML, script.src), requiring values to be wrapped in cryptographically trusted type wrappers created by vetted sanitization policies.
Answer: Vulnerabilities that permit attackers to compromise passwords, session tokens, or exploit implementation flaws (session fixation, missing MFA, session identifiers in URLs).
Answer: Session Fixation: The attacker tricks the victim into using a known session identifier provided by the attacker before login. Session Hijacking: The attacker steals an active session token after the victim has already authenticated.
Answer: When untrusted serialized objects (Java serialization, Python pickle, PHP serialize) are deserialized by the backend, allowing attackers to manipulate object state or trigger remote code execution (RCE) via gadget chains.
Answer: When an application automatically binds all client request fields directly to database model properties (e.g. User.update(req.body)), allowing an attacker to inject privileged fields like isAdmin: true or verified: true.
Answer: Every module, process, user, and service account must possess only the bare minimum permissions required to perform its valid function, minimizing blast radius upon compromise.
Answer: Stealing sensitive data from dynamic JSON endpoints by including them via <script src="..."> tags on an attacker's domain. Defense: Use Content-Type: application/json and prefix JSON responses with )]}', to cause syntax errors if parsed as a script.
Answer: An attack based on information gained from the implementation of a computer system (timing variations, power consumption, electromagnetic leaks) rather than weaknesses in the algorithm itself.
Answer: A cryptographic protocol where both client and server authenticate each other using X.509 digital certificates, ensuring zero-trust microservice communication within service meshes.
Answer: Disable XML external entity resolution and DTD processing in XML parsers (disallow-doctype-decl = true).
Answer: Restricting request volume over time. Common algorithms: Token Bucket, Leaky Bucket, Fixed Window Counter, and Sliding Window Log.
Answer: Relying on the secrecy of implementation details (e.g., hiding admin URLs, using non-standard ports) instead of robust cryptographic security. Kerckhoffs's principle dictates that a system must remain secure even if everything about the system is public knowledge except the key.
Answer: Insecure Direct Object Reference: an access control flaw where an application exposes a reference to an internal object (database ID, filename) without verifying user authorization.
Answer: Clears browser storage (cookies, cache, storage, execution contexts) upon logout: Clear-Site-Data: "cache", "cookies", "storage".
Answer: The header and payload are hashed together with a secret key or verified against a public key. If any byte in the header or payload changes, the signature verification fails immediately.
Answer: Inducing a backend server to make unauthorized HTTP or socket requests to internal infrastructure or external systems on behalf of the attacker.
Answer: Configures an endpoint where browsers send automated JSON violation reports when a CSP policy violation is detected or blocked.
Answer: Crafting malicious input that exploits catastrophic backtracking in polynomial or exponential regular expressions, consuming 100% CPU and halting the event loop.
Answer: A security model based on the principle "never trust, always verify": all requests, whether from outside or inside the corporate network, must be fully authenticated, authorized, and encrypted before granting access.
Answer: Validate object keys to disallow __proto__, constructor, and prototype, use Object.create(null) for key-value dictionaries, or freeze prototypes with Object.freeze(Object.prototype).
Answer:
- SAST (Static Application Security Testing): Analyzes source code for security flaws without executing the code.
- DAST (Dynamic Application Security Testing): Tests running applications from the outside by executing automated simulated attacks.
Answer: Software systems continuously evolve through dependency updates, infrastructure mutations, and emerging zero-day vulnerabilities. True enterprise security demands automated CI/CD guardrails, threat modeling, dependency scanning, runtime observability, and regular penetration testing.
| Defensive Control | Implementation Standard |
|---|---|
| XSS Defense | Strict CSP v3 (script-src 'nonce-...' 'strict-dynamic'), DOMPurify, Trusted Types |
| CSRF Defense | SameSite=Strict cookies, Synchronizer Token Pattern |
| Clickjacking | Content-Security-Policy: frame-ancestors 'none'; |
| Password Hashing | Argon2id (memoryCost: 65536, timeCost: 3, parallelism: 4) |
| Session Cookies | __Host- prefix, Secure, HttpOnly, SameSite=Strict, Path=/ |
| SSRF Guard | Pre-resolve DNS, block private CIDRs (10.0.0.0/8, 169.254.0.0/16, 127.0.0.0/8) |
| MIME Sniffing | X-Content-Type-Options: nosniff |
| Transport Security | Strict-Transport-Security: max-age=31536000; includeSubDomains; preload |
Maintained with ❤️ by Manthan Ank. Distributed under the MIT License.