secure-coding-rules 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,138 @@
1
+ # Authentication Security Rules
2
+
3
+ > OWASP Top 10 2025 - A07: Authentication Failures
4
+
5
+ ## Rules
6
+
7
+ ### 1. Implement Multi-Factor Authentication
8
+ - **DO**: Require MFA for all privileged accounts and offer it for all users. Use TOTP, WebAuthn, or push-based verification.
9
+ - **DON'T**: Rely solely on passwords for authentication, especially for admin and financial operations.
10
+ - **WHY**: Passwords alone are frequently compromised through phishing, credential stuffing, or data breaches.
11
+
12
+ ### 2. Enforce Strong Password Policies
13
+ - **DO**: Require a minimum of 8 characters. Check against breached password databases (e.g., Have I Been Pwned API). Allow long passphrases.
14
+ - **DON'T**: Impose arbitrary complexity rules (uppercase + number + symbol) that lead to predictable patterns, or cap password length below 64 characters.
15
+ - **WHY**: NIST guidelines (SP 800-63B) recommend length over complexity. Breached password checks are more effective than complexity rules.
16
+
17
+ ### 3. Protect Against Credential Stuffing and Brute Force
18
+ - **DO**: Implement rate limiting, account lockout with exponential backoff, and CAPTCHA after failed attempts.
19
+ - **DON'T**: Allow unlimited login attempts or reveal whether the username or password was incorrect.
20
+ - **WHY**: Credential stuffing uses leaked credentials from other breaches. Rate limiting and generic error messages slow automated attacks.
21
+
22
+ ### 4. Secure Session Management
23
+ - **DO**: Generate random session IDs with high entropy. Set cookies with `HttpOnly`, `Secure`, `SameSite=Strict`, and appropriate expiry.
24
+ - **DON'T**: Store session tokens in `localStorage` or expose them in URLs. Never accept session IDs from query parameters.
25
+ - **WHY**: Predictable or exposed session tokens allow session hijacking. Secure cookie attributes prevent XSS and CSRF-based theft.
26
+
27
+ ### 5. Implement Secure Password Reset
28
+ - **DO**: Use time-limited, single-use tokens for password reset. Send reset links only to verified email addresses. Invalidate all sessions on password change.
29
+ - **DON'T**: Use knowledge-based questions, send plaintext passwords, or allow token reuse.
30
+ - **WHY**: Weak password reset flows are as dangerous as weak passwords. They are a common target for account takeover.
31
+
32
+ ### 6. Validate JWTs Properly
33
+ - **DO**: Verify signature, issuer (`iss`), audience (`aud`), and expiration (`exp`). Use asymmetric algorithms (RS256, ES256) for distributed systems.
34
+ - **DON'T**: Use `alg: "none"`, accept tokens without signature verification, or store sensitive data in JWT payloads.
35
+ - **WHY**: JWT misuse (algorithm confusion, missing validation) leads to authentication bypass and privilege escalation.
36
+
37
+ ### 7. Implement Secure OAuth/OIDC Flows
38
+ - **DO**: Use Authorization Code flow with PKCE. Validate the `state` parameter and token claims. Store tokens securely.
39
+ - **DON'T**: Use Implicit flow for SPAs. Skip `state` validation or accept tokens from untrusted sources.
40
+ - **WHY**: OAuth misconfiguration enables token theft, CSRF, and account takeover through redirect manipulation.
41
+
42
+ ## Code Examples
43
+
44
+ ### Bad Practice
45
+ ```javascript
46
+ // Revealing whether user exists
47
+ app.post("/api/login", async (req, res) => {
48
+ const user = await db.findUser(req.body.email);
49
+ if (!user) return res.status(401).json({ error: "User not found" }); // Info leak
50
+ if (!checkPassword(req.body.password, user.password)) {
51
+ return res.status(401).json({ error: "Wrong password" }); // Info leak
52
+ }
53
+ res.json({ token: jwt.sign({ id: user.id }, SECRET) });
54
+ });
55
+
56
+ // Insecure JWT validation
57
+ const payload = jwt.decode(token); // decode without verify!
58
+ if (payload.role === "admin") grantAdminAccess();
59
+
60
+ // Session token in localStorage
61
+ localStorage.setItem("token", response.data.token); // Accessible via XSS
62
+ ```
63
+
64
+ ### Good Practice
65
+ ```javascript
66
+ import jwt from "jsonwebtoken";
67
+ import { rateLimit } from "express-rate-limit";
68
+
69
+ // Rate-limited login with generic error messages
70
+ const loginLimiter = rateLimit({
71
+ windowMs: 15 * 60 * 1000,
72
+ max: 10,
73
+ skipSuccessfulRequests: true,
74
+ });
75
+
76
+ app.post("/api/login", loginLimiter, async (req, res) => {
77
+ const { email, password } = req.body;
78
+ const user = await db.findUser(email);
79
+
80
+ // Constant-time check - same response whether user exists or not
81
+ const isValid = user ? await verifyPassword(password, user.passwordHash) : false;
82
+ if (!isValid) {
83
+ return res.status(401).json({ error: "Invalid credentials" }); // Generic message
84
+ }
85
+
86
+ // Check for MFA
87
+ if (user.mfaEnabled) {
88
+ const mfaToken = crypto.randomBytes(32).toString("hex");
89
+ await storeMfaChallenge(user.id, mfaToken, Date.now() + 300_000);
90
+ return res.json({ requiresMFA: true, mfaToken });
91
+ }
92
+
93
+ setSessionCookie(res, user);
94
+ res.json({ success: true });
95
+ });
96
+
97
+ // Secure session cookie
98
+ function setSessionCookie(res, user) {
99
+ const sessionId = crypto.randomUUID();
100
+ res.cookie("session", sessionId, {
101
+ httpOnly: true,
102
+ secure: true,
103
+ sameSite: "strict",
104
+ maxAge: 3600_000, // 1 hour
105
+ path: "/",
106
+ });
107
+ }
108
+
109
+ // Proper JWT verification
110
+ function verifyToken(token) {
111
+ return jwt.verify(token, PUBLIC_KEY, {
112
+ algorithms: ["ES256"], // Explicit algorithm
113
+ issuer: "https://auth.example.com",
114
+ audience: "https://api.example.com",
115
+ clockTolerance: 30,
116
+ });
117
+ }
118
+
119
+ // Secure password reset
120
+ app.post("/api/password-reset", async (req, res) => {
121
+ const token = crypto.randomBytes(32).toString("hex");
122
+ const hashedToken = crypto.createHash("sha256").update(token).digest("hex");
123
+ await db.storeResetToken(req.body.email, hashedToken, Date.now() + 3600_000);
124
+ await sendResetEmail(req.body.email, token); // Send unhashed token
125
+ res.json({ message: "If the email exists, a reset link has been sent" }); // Generic
126
+ });
127
+ ```
128
+
129
+ ## Quick Checklist
130
+ - [ ] MFA available and enforced for privileged accounts
131
+ - [ ] Passwords checked against breached password databases
132
+ - [ ] Login rate limiting and account lockout implemented
133
+ - [ ] Generic error messages for authentication failures (no user enumeration)
134
+ - [ ] Session cookies use `HttpOnly`, `Secure`, `SameSite=Strict`
135
+ - [ ] JWTs verified with explicit algorithm, issuer, and audience
136
+ - [ ] Password reset uses time-limited, single-use tokens
137
+ - [ ] All sessions invalidated on password change
138
+ - [ ] OAuth flows use PKCE and validate `state` parameter
@@ -0,0 +1,114 @@
1
+ # Cryptographic Failures Security Rules
2
+
3
+ > OWASP Top 10 2025 - A04: Cryptographic Failures
4
+
5
+ ## Rules
6
+
7
+ ### 1. Use Strong, Modern Encryption Algorithms
8
+ - **DO**: Use AES-256-GCM for symmetric encryption and RSA-OAEP or ECDH for asymmetric encryption. Use the Web Crypto API or `node:crypto` module.
9
+ - **DON'T**: Use deprecated algorithms like DES, 3DES, RC4, MD5, or SHA-1 for security purposes.
10
+ - **WHY**: Weak algorithms are vulnerable to known attacks and can be broken with modern hardware.
11
+
12
+ ### 2. Never Hardcode Secrets or Keys
13
+ - **DO**: Store keys in environment variables, secret managers (AWS Secrets Manager, HashiCorp Vault), or hardware security modules.
14
+ - **DON'T**: Embed encryption keys, API keys, or passwords directly in source code or config files.
15
+ - **WHY**: Hardcoded secrets in source code are easily extracted from repositories and compiled artifacts.
16
+
17
+ ### 3. Hash Passwords with Adaptive Algorithms
18
+ - **DO**: Use bcrypt, scrypt, or Argon2id for password hashing with appropriate work factors.
19
+ - **DON'T**: Use plain hashing (SHA-256, MD5) or unsalted hashes for passwords.
20
+ - **WHY**: Adaptive hashing algorithms are designed to resist GPU-based brute force and rainbow table attacks.
21
+
22
+ ### 4. Generate Cryptographically Secure Random Values
23
+ - **DO**: Use `crypto.randomBytes()`, `crypto.randomUUID()`, or `crypto.getRandomValues()` for tokens, IDs, and nonces.
24
+ - **DON'T**: Use `Math.random()` for any security-sensitive value (tokens, session IDs, OTPs).
25
+ - **WHY**: `Math.random()` is predictable and not cryptographically secure. Attackers can predict its output.
26
+
27
+ ### 5. Encrypt Sensitive Data at Rest and in Transit
28
+ - **DO**: Use TLS 1.2+ for data in transit. Encrypt PII, financial data, and health records at rest with field-level encryption where appropriate.
29
+ - **DON'T**: Store sensitive data in plaintext in databases, logs, or local storage.
30
+ - **WHY**: Data breaches expose plaintext data. Encryption limits the impact of unauthorized access.
31
+
32
+ ### 6. Use Authenticated Encryption
33
+ - **DO**: Use AEAD modes like AES-GCM that provide both confidentiality and integrity.
34
+ - **DON'T**: Use ECB mode or CBC without HMAC. Never implement custom encryption schemes.
35
+ - **WHY**: Unauthenticated encryption is vulnerable to padding oracle and ciphertext manipulation attacks.
36
+
37
+ ### 7. Manage Key Rotation and Lifecycle
38
+ - **DO**: Implement key rotation policies. Support decryption with old keys while encrypting with current keys.
39
+ - **DON'T**: Use the same encryption key indefinitely without rotation.
40
+ - **WHY**: Key rotation limits the impact of key compromise and meets compliance requirements.
41
+
42
+ ## Code Examples
43
+
44
+ ### Bad Practice
45
+ ```javascript
46
+ import crypto from "node:crypto";
47
+
48
+ // Using Math.random for tokens
49
+ const resetToken = Math.random().toString(36).substring(2);
50
+
51
+ // MD5 for password hashing (no salt, fast hash)
52
+ const hashedPassword = crypto.createHash("md5").update(password).digest("hex");
53
+
54
+ // Hardcoded encryption key
55
+ const ENCRYPTION_KEY = "my-super-secret-key-12345678";
56
+
57
+ // ECB mode (insecure - identical blocks produce identical ciphertext)
58
+ const cipher = crypto.createCipheriv("aes-256-ecb", key, null);
59
+ ```
60
+
61
+ ### Good Practice
62
+ ```javascript
63
+ import crypto from "node:crypto";
64
+ import { hash, verify } from "@node-rs/argon2"; // or bcrypt
65
+
66
+ // Cryptographically secure random token
67
+ const resetToken = crypto.randomBytes(32).toString("hex");
68
+ const sessionId = crypto.randomUUID();
69
+
70
+ // Argon2id password hashing
71
+ async function hashPassword(password) {
72
+ return hash(password, {
73
+ memoryCost: 65536, // 64 MB
74
+ timeCost: 3,
75
+ parallelism: 4,
76
+ });
77
+ }
78
+
79
+ async function verifyPassword(password, hashedPassword) {
80
+ return verify(hashedPassword, password);
81
+ }
82
+
83
+ // AES-256-GCM authenticated encryption
84
+ function encrypt(plaintext, key) {
85
+ const iv = crypto.randomBytes(12); // 96-bit IV for GCM
86
+ const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
87
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
88
+ const authTag = cipher.getAuthTag();
89
+ return Buffer.concat([iv, authTag, encrypted]).toString("base64");
90
+ }
91
+
92
+ function decrypt(ciphertext, key) {
93
+ const data = Buffer.from(ciphertext, "base64");
94
+ const iv = data.subarray(0, 12);
95
+ const authTag = data.subarray(12, 28);
96
+ const encrypted = data.subarray(28);
97
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
98
+ decipher.setAuthTag(authTag);
99
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
100
+ }
101
+
102
+ // Key from environment / secret manager
103
+ const encryptionKey = Buffer.from(process.env.ENCRYPTION_KEY, "base64");
104
+ ```
105
+
106
+ ## Quick Checklist
107
+ - [ ] Only modern algorithms used (AES-256-GCM, SHA-256+, Argon2id/bcrypt)
108
+ - [ ] No hardcoded keys or secrets in source code
109
+ - [ ] Passwords hashed with Argon2id, bcrypt, or scrypt
110
+ - [ ] All random values use `crypto.randomBytes()` or `crypto.getRandomValues()`
111
+ - [ ] Sensitive data encrypted at rest and in transit (TLS 1.2+)
112
+ - [ ] Authenticated encryption mode (GCM) used for symmetric encryption
113
+ - [ ] Key rotation policy defined and implemented
114
+ - [ ] No sensitive data in logs, URLs, or client-side storage
@@ -0,0 +1,144 @@
1
+ # Software and Data Integrity Security Rules
2
+
3
+ > OWASP Top 10 2025 - A08: Software or Data Integrity Failures
4
+
5
+ ## Rules
6
+
7
+ ### 1. Verify CI/CD Pipeline Integrity
8
+ - **DO**: Sign commits and artifacts. Use protected branches, required reviews, and immutable build environments.
9
+ - **DON'T**: Allow unapproved changes to CI/CD configuration or build scripts without review.
10
+ - **WHY**: Compromised pipelines can inject malicious code into production builds, affecting all users.
11
+
12
+ ### 2. Validate Data Integrity on Deserialization
13
+ - **DO**: Validate and sanitize all deserialized data against strict schemas. Use safe serialization formats (JSON with schema validation).
14
+ - **DON'T**: Deserialize untrusted data without validation, especially with `eval()`, `Function()`, or `node:vm`.
15
+ - **WHY**: Insecure deserialization can lead to Remote Code Execution (RCE) or data tampering.
16
+
17
+ ### 3. Implement Integrity Checks for Critical Data
18
+ - **DO**: Use HMAC or digital signatures to verify integrity of sensitive data (tokens, cookies, inter-service messages).
19
+ - **DON'T**: Trust data from cookies, hidden form fields, or client-side storage without integrity verification.
20
+ - **WHY**: Client-side data can be tampered with. Signed data ensures it hasn't been modified.
21
+
22
+ ### 4. Secure Auto-Update Mechanisms
23
+ - **DO**: Verify digital signatures on all updates before applying them. Use TLS for update channels.
24
+ - **DON'T**: Download and execute updates without cryptographic verification.
25
+ - **WHY**: Unsigned updates can be replaced with malicious payloads through MITM attacks or compromised update servers.
26
+
27
+ ### 5. Protect Database Migrations and Seeds
28
+ - **DO**: Review and version-control all database migrations. Use checksums to verify migration integrity.
29
+ - **DON'T**: Run auto-generated migrations in production without review or allow dynamic schema changes from user input.
30
+ - **WHY**: Malicious migrations can alter database structure, delete data, or create backdoor accounts.
31
+
32
+ ### 6. Validate Webhook and API Payloads
33
+ - **DO**: Verify webhook signatures using HMAC. Validate the payload schema and source IP where possible.
34
+ - **DON'T**: Process webhook payloads without signature verification or trust arbitrary callback URLs.
35
+ - **WHY**: Unverified webhooks allow attackers to inject fake events (payment confirmations, deploy triggers).
36
+
37
+ ## Code Examples
38
+
39
+ ### Bad Practice
40
+ ```javascript
41
+ // Deserializing untrusted data unsafely
42
+ const userData = eval(`(${req.body.data})`); // RCE vulnerability
43
+
44
+ // Trusting client-side data without integrity check
45
+ app.post("/api/checkout", (req, res) => {
46
+ const { price } = req.body; // Client can modify price
47
+ processPayment(price);
48
+ });
49
+
50
+ // Processing webhook without signature verification
51
+ app.post("/webhook/payment", (req, res) => {
52
+ const event = req.body;
53
+ if (event.type === "payment_success") {
54
+ fulfillOrder(event.orderId); // Could be forged
55
+ }
56
+ });
57
+ ```
58
+
59
+ ### Good Practice
60
+ ```javascript
61
+ import crypto from "node:crypto";
62
+ import { z } from "zod";
63
+
64
+ // Safe deserialization with schema validation
65
+ const UserDataSchema = z.object({
66
+ name: z.string().max(100),
67
+ email: z.string().email(),
68
+ role: z.enum(["user", "editor"]),
69
+ });
70
+
71
+ app.post("/api/profile", authenticate, (req, res) => {
72
+ const userData = UserDataSchema.parse(JSON.parse(req.body.data));
73
+ updateProfile(req.user.id, userData);
74
+ });
75
+
76
+ // Signed data for client-side integrity
77
+ function signData(data, secret) {
78
+ const payload = JSON.stringify(data);
79
+ const signature = crypto
80
+ .createHmac("sha256", secret)
81
+ .update(payload)
82
+ .digest("hex");
83
+ return { payload, signature };
84
+ }
85
+
86
+ function verifyData(payload, signature, secret) {
87
+ const expected = crypto
88
+ .createHmac("sha256", secret)
89
+ .update(payload)
90
+ .digest("hex");
91
+ if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
92
+ throw new Error("Data integrity verification failed");
93
+ }
94
+ return JSON.parse(payload);
95
+ }
96
+
97
+ // Server-side price validation (never trust client price)
98
+ app.post("/api/checkout", authenticate, async (req, res) => {
99
+ const { itemId, quantity } = req.body;
100
+ const item = await db.getItem(itemId); // Get real price from DB
101
+ const totalPrice = item.price * quantity; // Calculate server-side
102
+ await processPayment(req.user.id, totalPrice);
103
+ });
104
+
105
+ // Webhook signature verification (Stripe example pattern)
106
+ function verifyWebhookSignature(payload, signature, secret) {
107
+ const [timestamp, hash] = parseSignatureHeader(signature);
108
+
109
+ // Prevent replay attacks
110
+ if (Date.now() / 1000 - Number(timestamp) > 300) {
111
+ throw new Error("Webhook timestamp too old");
112
+ }
113
+
114
+ const expected = crypto
115
+ .createHmac("sha256", secret)
116
+ .update(`${timestamp}.${payload}`)
117
+ .digest("hex");
118
+
119
+ if (!crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(expected))) {
120
+ throw new Error("Invalid webhook signature");
121
+ }
122
+ return JSON.parse(payload);
123
+ }
124
+
125
+ app.post("/webhook/payment", express.raw({ type: "application/json" }), (req, res) => {
126
+ const event = verifyWebhookSignature(
127
+ req.body.toString(),
128
+ req.headers["x-signature"],
129
+ process.env.WEBHOOK_SECRET,
130
+ );
131
+ processVerifiedEvent(event);
132
+ res.sendStatus(200);
133
+ });
134
+ ```
135
+
136
+ ## Quick Checklist
137
+ - [ ] CI/CD pipeline changes require review and approval
138
+ - [ ] All deserialization uses schema validation, never `eval()`
139
+ - [ ] Critical client-facing data is signed with HMAC or digital signatures
140
+ - [ ] Server-side calculation for prices, totals, and business-critical values
141
+ - [ ] Webhook payloads verified with signature before processing
142
+ - [ ] Auto-updates verify digital signatures before applying
143
+ - [ ] Database migrations are version-controlled and reviewed
144
+ - [ ] `timingSafeEqual` used for all signature comparisons
@@ -0,0 +1,180 @@
1
+ # Error Handling Security Rules
2
+
3
+ > OWASP Top 10 2025 - A10: Mishandling of Exceptional Conditions (NEW)
4
+
5
+ ## Rules
6
+
7
+ ### 1. Implement Fail-Safe Defaults
8
+ - **DO**: Default to the most secure state when errors occur. Deny access, reject transactions, and close connections on failure.
9
+ - **DON'T**: Fail open by granting access or skipping validation when an error occurs.
10
+ - **WHY**: Attackers intentionally trigger errors to bypass security controls. Fail-safe defaults ensure errors never weaken security posture.
11
+
12
+ ### 2. Use Structured Error Handling Patterns
13
+ - **DO**: Use try-catch blocks, Result/Either patterns, or error boundary components. Handle every possible error state explicitly.
14
+ - **DON'T**: Ignore promise rejections, leave catch blocks empty, or rely on uncaught exception handlers for control flow.
15
+ - **WHY**: Unhandled errors crash services, leak information, and create unpredictable system states that attackers exploit.
16
+
17
+ ### 3. Separate Internal and External Error Messages
18
+ - **DO**: Log detailed errors internally with full context. Return generic, safe error messages to users with a reference ID.
19
+ - **DON'T**: Expose internal error details, stack traces, or database messages to clients.
20
+ - **WHY**: Detailed errors reveal technology stack, file paths, and database structure that aid targeted attacks.
21
+
22
+ ### 4. Implement Graceful Degradation
23
+ - **DO**: Design fallback behavior for external service failures. Use circuit breakers, timeouts, and retry with backoff.
24
+ - **DON'T**: Let a single external dependency failure cascade into full application failure.
25
+ - **WHY**: Cascading failures cause outages. Graceful degradation maintains core functionality and prevents denial of service.
26
+
27
+ ### 5. Handle All Promise Rejections and Async Errors
28
+ - **DO**: Attach `.catch()` to all promises or use `try-catch` with `await`. Register global `unhandledRejection` handlers as a safety net.
29
+ - **DON'T**: Fire and forget promises or assume async operations will always succeed.
30
+ - **WHY**: Unhandled promise rejections cause silent failures, memory leaks, and in Node.js 15+, process crashes.
31
+
32
+ ### 6. Validate Error Objects Before Processing
33
+ - **DO**: Verify error types and properties before accessing them. Use `instanceof` checks or error codes.
34
+ - **DON'T**: Assume all caught errors are `Error` instances or have expected properties.
35
+ - **WHY**: JavaScript can throw any value. Accessing `.message` or `.stack` on non-Error values causes secondary failures.
36
+
37
+ ### 7. Implement Request Timeouts and Resource Limits
38
+ - **DO**: Set timeouts for all external requests, database queries, and file operations. Limit request body size and processing time.
39
+ - **DON'T**: Allow requests or operations to run indefinitely without timeout boundaries.
40
+ - **WHY**: Missing timeouts lead to resource exhaustion, blocked event loops, and denial of service.
41
+
42
+ ## Code Examples
43
+
44
+ ### Bad Practice
45
+ ```javascript
46
+ // Failing open - granting access on error
47
+ async function checkPermission(userId, resource) {
48
+ try {
49
+ const allowed = await authService.check(userId, resource);
50
+ return allowed;
51
+ } catch (error) {
52
+ return true; // DANGEROUS: fail-open grants access on auth service failure
53
+ }
54
+ }
55
+
56
+ // Empty catch block hiding errors
57
+ try {
58
+ await processPayment(order);
59
+ } catch (e) {
60
+ // silently swallowed - payment may have partially processed
61
+ }
62
+
63
+ // Leaking error details to client
64
+ app.use((err, req, res, next) => {
65
+ res.status(500).json({
66
+ error: err.message,
67
+ stack: err.stack,
68
+ sql: err.query,
69
+ });
70
+ });
71
+
72
+ // No timeout on external request
73
+ const response = await fetch("https://external-api.com/data"); // Hangs forever if API is down
74
+ ```
75
+
76
+ ### Good Practice
77
+ ```javascript
78
+ // Fail-safe: deny access on error
79
+ async function checkPermission(userId, resource) {
80
+ try {
81
+ return await authService.check(userId, resource);
82
+ } catch (error) {
83
+ logger.error({ userId, resource, error: error.message }, "Auth service failed");
84
+ return false; // Fail-safe: deny access on error
85
+ }
86
+ }
87
+
88
+ // Structured error handling with Result pattern
89
+ class Result {
90
+ constructor(ok, value, error) {
91
+ this.ok = ok;
92
+ this.value = value;
93
+ this.error = error;
94
+ }
95
+ static success(value) { return new Result(true, value, null); }
96
+ static failure(error) { return new Result(false, null, error); }
97
+ }
98
+
99
+ async function processPayment(order) {
100
+ try {
101
+ const result = await paymentGateway.charge(order);
102
+ return Result.success(result);
103
+ } catch (error) {
104
+ logger.error({ orderId: order.id, error: error.message }, "Payment failed");
105
+ return Result.failure(new PaymentError("Payment processing failed", { cause: error }));
106
+ }
107
+ }
108
+
109
+ // Safe error handler separating internal/external messages
110
+ app.use((err, req, res, next) => {
111
+ const errorId = crypto.randomUUID();
112
+ logger.error({ errorId, path: req.path, method: req.method, error: err.message, stack: err.stack });
113
+ const status = err.status ?? 500;
114
+ res.status(status).json({
115
+ error: status < 500 ? err.message : "An internal error occurred",
116
+ errorId,
117
+ });
118
+ });
119
+
120
+ // Circuit breaker for external services
121
+ class CircuitBreaker {
122
+ #failures = 0;
123
+ #lastFailure = 0;
124
+ #state = "closed"; // closed, open, half-open
125
+
126
+ constructor(threshold = 5, resetTimeout = 30_000) {
127
+ this.threshold = threshold;
128
+ this.resetTimeout = resetTimeout;
129
+ }
130
+
131
+ async execute(fn) {
132
+ if (this.#state === "open") {
133
+ if (Date.now() - this.#lastFailure > this.resetTimeout) {
134
+ this.#state = "half-open";
135
+ } else {
136
+ throw new Error("Circuit breaker is open");
137
+ }
138
+ }
139
+ try {
140
+ const result = await fn();
141
+ this.#failures = 0;
142
+ this.#state = "closed";
143
+ return result;
144
+ } catch (error) {
145
+ this.#failures++;
146
+ this.#lastFailure = Date.now();
147
+ if (this.#failures >= this.threshold) this.#state = "open";
148
+ throw error;
149
+ }
150
+ }
151
+ }
152
+
153
+ // Request with timeout
154
+ async function fetchWithTimeout(url, options = {}, timeoutMs = 5000) {
155
+ const controller = new AbortController();
156
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
157
+ try {
158
+ return await fetch(url, { ...options, signal: controller.signal });
159
+ } finally {
160
+ clearTimeout(timeoutId);
161
+ }
162
+ }
163
+
164
+ // Global safety net (not a substitute for proper error handling)
165
+ process.on("unhandledRejection", (reason) => {
166
+ logger.fatal({ reason: String(reason) }, "Unhandled promise rejection");
167
+ process.exitCode = 1;
168
+ });
169
+ ```
170
+
171
+ ## Quick Checklist
172
+ - [ ] All error paths fail-safe (deny access, reject transactions)
173
+ - [ ] No empty catch blocks in the codebase
174
+ - [ ] Internal errors logged with full detail; external responses are generic
175
+ - [ ] Circuit breakers implemented for external service calls
176
+ - [ ] All promises have `.catch()` or are inside try-catch with await
177
+ - [ ] Global `unhandledRejection` handler registered as safety net
178
+ - [ ] Timeouts set for all HTTP requests, database queries, and I/O operations
179
+ - [ ] Request body size limits enforced
180
+ - [ ] Error objects validated before accessing properties