tlc-claude-code 1.4.8 → 1.4.9

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.
Files changed (169) hide show
  1. package/package.json +1 -1
  2. package/server/index.js +229 -14
  3. package/server/lib/compliance/control-mapper.js +401 -0
  4. package/server/lib/compliance/control-mapper.test.js +117 -0
  5. package/server/lib/compliance/evidence-linker.js +296 -0
  6. package/server/lib/compliance/evidence-linker.test.js +121 -0
  7. package/server/lib/compliance/gdpr-checklist.js +416 -0
  8. package/server/lib/compliance/gdpr-checklist.test.js +131 -0
  9. package/server/lib/compliance/hipaa-checklist.js +277 -0
  10. package/server/lib/compliance/hipaa-checklist.test.js +101 -0
  11. package/server/lib/compliance/iso27001-checklist.js +287 -0
  12. package/server/lib/compliance/iso27001-checklist.test.js +99 -0
  13. package/server/lib/compliance/multi-framework-reporter.js +284 -0
  14. package/server/lib/compliance/multi-framework-reporter.test.js +127 -0
  15. package/server/lib/compliance/pci-dss-checklist.js +214 -0
  16. package/server/lib/compliance/pci-dss-checklist.test.js +95 -0
  17. package/server/lib/compliance/trust-centre.js +187 -0
  18. package/server/lib/compliance/trust-centre.test.js +93 -0
  19. package/server/lib/dashboard/api-server.js +155 -0
  20. package/server/lib/dashboard/api-server.test.js +155 -0
  21. package/server/lib/dashboard/health-api.js +199 -0
  22. package/server/lib/dashboard/health-api.test.js +122 -0
  23. package/server/lib/dashboard/notes-api.js +234 -0
  24. package/server/lib/dashboard/notes-api.test.js +134 -0
  25. package/server/lib/dashboard/router-api.js +176 -0
  26. package/server/lib/dashboard/router-api.test.js +132 -0
  27. package/server/lib/dashboard/tasks-api.js +289 -0
  28. package/server/lib/dashboard/tasks-api.test.js +161 -0
  29. package/server/lib/dashboard/tlc-introspection.js +197 -0
  30. package/server/lib/dashboard/tlc-introspection.test.js +138 -0
  31. package/server/lib/dashboard/version-api.js +222 -0
  32. package/server/lib/dashboard/version-api.test.js +112 -0
  33. package/server/lib/dashboard/websocket-server.js +104 -0
  34. package/server/lib/dashboard/websocket-server.test.js +118 -0
  35. package/server/lib/deploy/branch-classifier.js +163 -0
  36. package/server/lib/deploy/branch-classifier.test.js +164 -0
  37. package/server/lib/deploy/deployment-approval.js +299 -0
  38. package/server/lib/deploy/deployment-approval.test.js +296 -0
  39. package/server/lib/deploy/deployment-audit.js +374 -0
  40. package/server/lib/deploy/deployment-audit.test.js +307 -0
  41. package/server/lib/deploy/deployment-executor.js +335 -0
  42. package/server/lib/deploy/deployment-executor.test.js +329 -0
  43. package/server/lib/deploy/deployment-rules.js +163 -0
  44. package/server/lib/deploy/deployment-rules.test.js +188 -0
  45. package/server/lib/deploy/rollback-manager.js +379 -0
  46. package/server/lib/deploy/rollback-manager.test.js +321 -0
  47. package/server/lib/deploy/security-gates.js +236 -0
  48. package/server/lib/deploy/security-gates.test.js +222 -0
  49. package/server/lib/k8s/gitops-config.js +188 -0
  50. package/server/lib/k8s/gitops-config.test.js +59 -0
  51. package/server/lib/k8s/helm-generator.js +196 -0
  52. package/server/lib/k8s/helm-generator.test.js +59 -0
  53. package/server/lib/k8s/kustomize-generator.js +176 -0
  54. package/server/lib/k8s/kustomize-generator.test.js +58 -0
  55. package/server/lib/k8s/network-policy.js +114 -0
  56. package/server/lib/k8s/network-policy.test.js +53 -0
  57. package/server/lib/k8s/pod-security.js +114 -0
  58. package/server/lib/k8s/pod-security.test.js +55 -0
  59. package/server/lib/k8s/rbac-generator.js +132 -0
  60. package/server/lib/k8s/rbac-generator.test.js +57 -0
  61. package/server/lib/k8s/resource-manager.js +172 -0
  62. package/server/lib/k8s/resource-manager.test.js +60 -0
  63. package/server/lib/k8s/secrets-encryption.js +168 -0
  64. package/server/lib/k8s/secrets-encryption.test.js +49 -0
  65. package/server/lib/monitoring/alert-manager.js +238 -0
  66. package/server/lib/monitoring/alert-manager.test.js +106 -0
  67. package/server/lib/monitoring/health-check.js +226 -0
  68. package/server/lib/monitoring/health-check.test.js +176 -0
  69. package/server/lib/monitoring/incident-manager.js +230 -0
  70. package/server/lib/monitoring/incident-manager.test.js +98 -0
  71. package/server/lib/monitoring/log-aggregator.js +147 -0
  72. package/server/lib/monitoring/log-aggregator.test.js +89 -0
  73. package/server/lib/monitoring/metrics-collector.js +337 -0
  74. package/server/lib/monitoring/metrics-collector.test.js +172 -0
  75. package/server/lib/monitoring/status-page.js +214 -0
  76. package/server/lib/monitoring/status-page.test.js +105 -0
  77. package/server/lib/monitoring/uptime-monitor.js +194 -0
  78. package/server/lib/monitoring/uptime-monitor.test.js +109 -0
  79. package/server/lib/network/fail2ban-config.js +294 -0
  80. package/server/lib/network/fail2ban-config.test.js +275 -0
  81. package/server/lib/network/firewall-manager.js +252 -0
  82. package/server/lib/network/firewall-manager.test.js +254 -0
  83. package/server/lib/network/geoip-filter.js +282 -0
  84. package/server/lib/network/geoip-filter.test.js +264 -0
  85. package/server/lib/network/rate-limiter.js +229 -0
  86. package/server/lib/network/rate-limiter.test.js +293 -0
  87. package/server/lib/network/request-validator.js +351 -0
  88. package/server/lib/network/request-validator.test.js +345 -0
  89. package/server/lib/network/security-headers.js +251 -0
  90. package/server/lib/network/security-headers.test.js +283 -0
  91. package/server/lib/network/tls-config.js +210 -0
  92. package/server/lib/network/tls-config.test.js +248 -0
  93. package/server/lib/security/auth-security.js +369 -0
  94. package/server/lib/security/auth-security.test.js +448 -0
  95. package/server/lib/security/cis-benchmark.js +152 -0
  96. package/server/lib/security/cis-benchmark.test.js +137 -0
  97. package/server/lib/security/compose-templates.js +312 -0
  98. package/server/lib/security/compose-templates.test.js +229 -0
  99. package/server/lib/security/container-runtime.js +456 -0
  100. package/server/lib/security/container-runtime.test.js +503 -0
  101. package/server/lib/security/cors-validator.js +278 -0
  102. package/server/lib/security/cors-validator.test.js +310 -0
  103. package/server/lib/security/crypto-utils.js +253 -0
  104. package/server/lib/security/crypto-utils.test.js +409 -0
  105. package/server/lib/security/dockerfile-linter.js +459 -0
  106. package/server/lib/security/dockerfile-linter.test.js +483 -0
  107. package/server/lib/security/dockerfile-templates.js +278 -0
  108. package/server/lib/security/dockerfile-templates.test.js +164 -0
  109. package/server/lib/security/error-sanitizer.js +426 -0
  110. package/server/lib/security/error-sanitizer.test.js +331 -0
  111. package/server/lib/security/headers-generator.js +368 -0
  112. package/server/lib/security/headers-generator.test.js +398 -0
  113. package/server/lib/security/image-scanner.js +83 -0
  114. package/server/lib/security/image-scanner.test.js +106 -0
  115. package/server/lib/security/input-validator.js +352 -0
  116. package/server/lib/security/input-validator.test.js +330 -0
  117. package/server/lib/security/network-policy.js +174 -0
  118. package/server/lib/security/network-policy.test.js +164 -0
  119. package/server/lib/security/output-encoder.js +237 -0
  120. package/server/lib/security/output-encoder.test.js +276 -0
  121. package/server/lib/security/path-validator.js +359 -0
  122. package/server/lib/security/path-validator.test.js +293 -0
  123. package/server/lib/security/query-builder.js +421 -0
  124. package/server/lib/security/query-builder.test.js +318 -0
  125. package/server/lib/security/secret-detector.js +290 -0
  126. package/server/lib/security/secret-detector.test.js +354 -0
  127. package/server/lib/security/secrets-validator.js +137 -0
  128. package/server/lib/security/secrets-validator.test.js +120 -0
  129. package/server/lib/security-testing/dast-runner.js +154 -0
  130. package/server/lib/security-testing/dast-runner.test.js +62 -0
  131. package/server/lib/security-testing/dependency-scanner.js +172 -0
  132. package/server/lib/security-testing/dependency-scanner.test.js +64 -0
  133. package/server/lib/security-testing/pentest-runner.js +230 -0
  134. package/server/lib/security-testing/pentest-runner.test.js +60 -0
  135. package/server/lib/security-testing/sast-runner.js +136 -0
  136. package/server/lib/security-testing/sast-runner.test.js +62 -0
  137. package/server/lib/security-testing/secret-scanner.js +153 -0
  138. package/server/lib/security-testing/secret-scanner.test.js +66 -0
  139. package/server/lib/security-testing/security-gate.js +216 -0
  140. package/server/lib/security-testing/security-gate.test.js +115 -0
  141. package/server/lib/security-testing/security-reporter.js +303 -0
  142. package/server/lib/security-testing/security-reporter.test.js +114 -0
  143. package/server/lib/standards/audit-checker.js +546 -0
  144. package/server/lib/standards/audit-checker.test.js +415 -0
  145. package/server/lib/standards/cleanup-executor.js +452 -0
  146. package/server/lib/standards/cleanup-executor.test.js +293 -0
  147. package/server/lib/standards/refactor-stepper.js +425 -0
  148. package/server/lib/standards/refactor-stepper.test.js +298 -0
  149. package/server/lib/standards/standards-injector.js +167 -0
  150. package/server/lib/standards/standards-injector.test.js +232 -0
  151. package/server/lib/user-management.test.js +284 -0
  152. package/server/lib/vps/backup-manager.js +157 -0
  153. package/server/lib/vps/backup-manager.test.js +59 -0
  154. package/server/lib/vps/caddy-config.js +159 -0
  155. package/server/lib/vps/caddy-config.test.js +48 -0
  156. package/server/lib/vps/compose-orchestrator.js +219 -0
  157. package/server/lib/vps/compose-orchestrator.test.js +50 -0
  158. package/server/lib/vps/database-config.js +208 -0
  159. package/server/lib/vps/database-config.test.js +47 -0
  160. package/server/lib/vps/deploy-script.js +211 -0
  161. package/server/lib/vps/deploy-script.test.js +53 -0
  162. package/server/lib/vps/secrets-manager.js +148 -0
  163. package/server/lib/vps/secrets-manager.test.js +58 -0
  164. package/server/lib/vps/server-hardening.js +174 -0
  165. package/server/lib/vps/server-hardening.test.js +70 -0
  166. package/server/package-lock.json +19 -0
  167. package/server/package.json +1 -0
  168. package/server/templates/CLAUDE.md +37 -0
  169. package/server/templates/CODING-STANDARDS.md +408 -0
@@ -0,0 +1,426 @@
1
+ /**
2
+ * Error Sanitizer Module
3
+ *
4
+ * Sanitizes error messages to prevent information disclosure.
5
+ * Addresses OWASP A01: Broken Access Control (info leakage)
6
+ */
7
+
8
+ import crypto from 'crypto';
9
+
10
+ /**
11
+ * Sensitive patterns to redact from error messages
12
+ */
13
+ const SENSITIVE_PATTERNS = [
14
+ // File paths
15
+ /(?:\/[\w.-]+)+(?:\.[\w]+)?/g,
16
+ // Windows paths
17
+ /[A-Z]:\\[\w\\.-]+/gi,
18
+ // Stack trace line numbers
19
+ /:\d+:\d+/g,
20
+ // IP addresses
21
+ /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g,
22
+ // Port numbers
23
+ /:\d{2,5}\b/g,
24
+ // Connection strings
25
+ /(?:postgresql|mysql|mongodb|redis):\/\/[^\s]+/gi,
26
+ // Environment variables in messages
27
+ /\$[A-Z_][A-Z0-9_]*/g,
28
+ // Memory addresses
29
+ /0x[0-9a-f]{8,}/gi,
30
+ ];
31
+
32
+ /**
33
+ * Sensitive property names to remove
34
+ */
35
+ const SENSITIVE_PROPERTIES = [
36
+ 'password', 'passwd', 'pass', 'secret', 'apiKey', 'api_key',
37
+ 'token', 'accessToken', 'access_token', 'refreshToken', 'refresh_token',
38
+ 'privateKey', 'private_key', 'credential', 'credentials',
39
+ ];
40
+
41
+ /**
42
+ * Patterns that indicate database errors
43
+ */
44
+ const DATABASE_ERROR_PATTERNS = [
45
+ /ECONNREFUSED/i,
46
+ /postgresql/i,
47
+ /mysql/i,
48
+ /mongodb/i,
49
+ /database/i,
50
+ /sql/i,
51
+ /syntax error/i,
52
+ /query/i,
53
+ ];
54
+
55
+ /**
56
+ * Status code to error code mapping
57
+ */
58
+ const STATUS_CODE_MAP = {
59
+ 400: 'BAD_REQUEST',
60
+ 401: 'UNAUTHORIZED',
61
+ 403: 'FORBIDDEN',
62
+ 404: 'NOT_FOUND',
63
+ 409: 'CONFLICT',
64
+ 422: 'UNPROCESSABLE_ENTITY',
65
+ 429: 'TOO_MANY_REQUESTS',
66
+ 500: 'INTERNAL_ERROR',
67
+ 502: 'BAD_GATEWAY',
68
+ 503: 'SERVICE_UNAVAILABLE',
69
+ };
70
+
71
+ /**
72
+ * Internal error codes for classification
73
+ */
74
+ export const ERROR_CODES = {
75
+ VALIDATION_ERROR: 'VALIDATION_ERROR',
76
+ AUTHENTICATION_ERROR: 'AUTHENTICATION_ERROR',
77
+ AUTHORIZATION_ERROR: 'AUTHORIZATION_ERROR',
78
+ NOT_FOUND: 'NOT_FOUND',
79
+ RATE_LIMITED: 'RATE_LIMITED',
80
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
81
+ DATABASE_ERROR: 'DATABASE_ERROR',
82
+ NETWORK_ERROR: 'NETWORK_ERROR',
83
+ };
84
+
85
+ /**
86
+ * Generate a unique error ID
87
+ * @returns {string} Unique error ID
88
+ */
89
+ function generateErrorId() {
90
+ return crypto.randomUUID();
91
+ }
92
+
93
+ /**
94
+ * Check if an error message indicates a database error
95
+ * @param {string} message - Error message
96
+ * @returns {boolean} True if database error
97
+ */
98
+ function isDatabaseError(message) {
99
+ return DATABASE_ERROR_PATTERNS.some((pattern) => pattern.test(message));
100
+ }
101
+
102
+ /**
103
+ * Redact sensitive information from a string
104
+ * @param {string} text - Text to redact
105
+ * @param {RegExp[]} additionalPatterns - Additional patterns to redact
106
+ * @returns {string} Redacted text
107
+ */
108
+ function redactSensitiveInfo(text, additionalPatterns = []) {
109
+ if (!text) return '';
110
+
111
+ let redacted = text;
112
+ const allPatterns = [...SENSITIVE_PATTERNS, ...additionalPatterns];
113
+
114
+ for (const pattern of allPatterns) {
115
+ redacted = redacted.replace(pattern, '[REDACTED]');
116
+ }
117
+
118
+ return redacted;
119
+ }
120
+
121
+ /**
122
+ * Sanitize an error for safe client response
123
+ * @param {Error|null|undefined|string} error - Error to sanitize
124
+ * @param {Object} options - Sanitization options
125
+ * @returns {Object} Sanitized error object
126
+ */
127
+ export function sanitizeError(error, options = {}) {
128
+ const {
129
+ production = true,
130
+ redactPatterns = [],
131
+ genericMessages = {},
132
+ } = options;
133
+
134
+ // Handle null/undefined
135
+ if (error === null || error === undefined) {
136
+ return {
137
+ message: 'An unexpected error occurred',
138
+ id: generateErrorId(),
139
+ };
140
+ }
141
+
142
+ // Handle non-Error objects
143
+ if (typeof error === 'string') {
144
+ return {
145
+ message: 'An error occurred',
146
+ id: generateErrorId(),
147
+ };
148
+ }
149
+
150
+ // Handle Error objects
151
+ const originalMessage = error.message || '';
152
+ const id = generateErrorId();
153
+ const result = { id };
154
+
155
+ // Check if user-friendly message should be preserved
156
+ if (error.isUserFriendly) {
157
+ result.message = originalMessage;
158
+ } else if (production) {
159
+ // Check for database errors
160
+ if (isDatabaseError(originalMessage)) {
161
+ result.message = genericMessages.database || 'A database error occurred';
162
+ } else if (!originalMessage) {
163
+ result.message = 'An unexpected error occurred';
164
+ } else {
165
+ // Redact sensitive info
166
+ const sanitized = redactSensitiveInfo(originalMessage, redactPatterns);
167
+ // If heavily redacted, use generic message
168
+ if (sanitized.includes('[REDACTED]') || sanitized.split('[REDACTED]').length > 2) {
169
+ result.message = 'An unexpected error occurred';
170
+ } else {
171
+ result.message = sanitized;
172
+ }
173
+ }
174
+ } else {
175
+ // Development mode - include more details without redaction
176
+ result.message = originalMessage;
177
+ if (error.stack) {
178
+ result.stack = error.stack;
179
+ }
180
+ if (error.cause) {
181
+ result.cause = error.cause;
182
+ }
183
+ }
184
+
185
+ // Remove sensitive properties
186
+ for (const prop of SENSITIVE_PROPERTIES) {
187
+ if (result[prop]) {
188
+ delete result[prop];
189
+ }
190
+ }
191
+
192
+ return result;
193
+ }
194
+
195
+ /**
196
+ * Determine if an error is operational (expected) vs programmer error
197
+ * @param {Error} error - Error to check
198
+ * @returns {boolean} True if operational
199
+ */
200
+ export function isOperationalError(error) {
201
+ if (!error) return false;
202
+
203
+ // Explicitly marked as operational
204
+ if (error.isOperational === true) return true;
205
+
206
+ // Status codes 4xx are operational
207
+ if (error.statusCode >= 400 && error.statusCode < 500) return true;
208
+
209
+ // TypeError, ReferenceError, etc. are programmer errors
210
+ if (error instanceof TypeError || error instanceof ReferenceError ||
211
+ error instanceof SyntaxError || error instanceof RangeError) {
212
+ return false;
213
+ }
214
+
215
+ // 5xx errors are not operational
216
+ if (error.statusCode >= 500) return false;
217
+
218
+ return false;
219
+ }
220
+
221
+ /**
222
+ * Format error for HTTP response
223
+ * @param {Error} error - Error to format
224
+ * @param {Object} options - Formatting options
225
+ * @returns {Object} Formatted response
226
+ */
227
+ export function formatErrorResponse(error, options = {}) {
228
+ const { production = true, includeStatus = false } = options;
229
+
230
+ const statusCode = error.statusCode || 500;
231
+ const code = STATUS_CODE_MAP[statusCode] || 'INTERNAL_ERROR';
232
+ const id = generateErrorId();
233
+
234
+ const errorObj = {
235
+ message: error.isUserFriendly ? error.message : (error.message || 'An error occurred'),
236
+ code,
237
+ id,
238
+ };
239
+
240
+ // Include status if requested
241
+ if (includeStatus) {
242
+ errorObj.status = statusCode;
243
+ }
244
+
245
+ // Include validation errors for 400
246
+ if (statusCode === 400 && error.validationErrors) {
247
+ errorObj.details = error.validationErrors;
248
+ }
249
+
250
+ // Include stack in development
251
+ if (!production && error.stack) {
252
+ errorObj.stack = error.stack;
253
+ }
254
+
255
+ return { error: errorObj };
256
+ }
257
+
258
+ /**
259
+ * Classify an error into a category
260
+ * @param {Error} error - Error to classify
261
+ * @returns {string} Error code
262
+ */
263
+ export function classifyError(error) {
264
+ if (!error) return ERROR_CODES.INTERNAL_ERROR;
265
+
266
+ const message = (error.message || '').toLowerCase();
267
+ const name = (error.name || '').toLowerCase();
268
+
269
+ // Check by error name
270
+ if (name.includes('validation') || name.includes('invalid')) {
271
+ return ERROR_CODES.VALIDATION_ERROR;
272
+ }
273
+
274
+ if (name.includes('auth') || name.includes('unauthorized')) {
275
+ return ERROR_CODES.AUTHENTICATION_ERROR;
276
+ }
277
+
278
+ if (name.includes('forbidden') || name.includes('permission')) {
279
+ return ERROR_CODES.AUTHORIZATION_ERROR;
280
+ }
281
+
282
+ if (name.includes('notfound') || name === 'notfounderror') {
283
+ return ERROR_CODES.NOT_FOUND;
284
+ }
285
+
286
+ // Check by error message
287
+ if (message.includes('not found') || message.includes('does not exist')) {
288
+ return ERROR_CODES.NOT_FOUND;
289
+ }
290
+
291
+ if (message.includes('invalid') || message.includes('required')) {
292
+ return ERROR_CODES.VALIDATION_ERROR;
293
+ }
294
+
295
+ if (message.includes('unauthorized') || message.includes('invalid credentials')) {
296
+ return ERROR_CODES.AUTHENTICATION_ERROR;
297
+ }
298
+
299
+ if (message.includes('forbidden') || message.includes('permission denied')) {
300
+ return ERROR_CODES.AUTHORIZATION_ERROR;
301
+ }
302
+
303
+ if (message.includes('rate limit') || message.includes('too many')) {
304
+ return ERROR_CODES.RATE_LIMITED;
305
+ }
306
+
307
+ if (isDatabaseError(message)) {
308
+ return ERROR_CODES.DATABASE_ERROR;
309
+ }
310
+
311
+ if (message.includes('connect') || message.includes('timeout') || message.includes('network')) {
312
+ return ERROR_CODES.NETWORK_ERROR;
313
+ }
314
+
315
+ // Check for database-specific error codes
316
+ if (error.code) {
317
+ const code = String(error.code);
318
+ if (code.startsWith('23') || code.startsWith('42')) {
319
+ return ERROR_CODES.DATABASE_ERROR;
320
+ }
321
+ if (code === 'ECONNREFUSED' || code === 'ETIMEDOUT') {
322
+ return ERROR_CODES.NETWORK_ERROR;
323
+ }
324
+ }
325
+
326
+ return ERROR_CODES.INTERNAL_ERROR;
327
+ }
328
+
329
+ /**
330
+ * Create an error sanitizer with preset configuration
331
+ * @param {Object} config - Sanitizer configuration
332
+ * @returns {Object} Error sanitizer instance
333
+ */
334
+ export function createErrorSanitizer(config = {}) {
335
+ const {
336
+ production = true,
337
+ redactPatterns = [],
338
+ genericMessages = {},
339
+ logger = null,
340
+ } = config;
341
+
342
+ return {
343
+ /**
344
+ * Sanitize an error
345
+ * @param {Error} error - Error to sanitize
346
+ * @param {Object} context - Additional context
347
+ * @returns {Object} Sanitized error
348
+ */
349
+ sanitize(error, context = {}) {
350
+ // Log original error if logger provided
351
+ if (logger) {
352
+ logger({
353
+ originalMessage: error.message,
354
+ stack: error.stack,
355
+ code: error.code,
356
+ ...context,
357
+ });
358
+ }
359
+
360
+ return sanitizeError(error, {
361
+ production,
362
+ redactPatterns,
363
+ genericMessages,
364
+ });
365
+ },
366
+
367
+ /**
368
+ * Express error handler middleware
369
+ */
370
+ middleware() {
371
+ return (error, req, res, next) => {
372
+ const sanitized = this.sanitize(error, {
373
+ requestId: req.id || req.headers['x-request-id'],
374
+ });
375
+
376
+ const statusCode = this.getStatusCode(error);
377
+ res.status(statusCode).json(sanitized);
378
+ };
379
+ },
380
+
381
+ /**
382
+ * Get HTTP status code for error
383
+ * @param {Error} error - Error to check
384
+ * @returns {number} HTTP status code
385
+ */
386
+ getStatusCode(error) {
387
+ if (error.statusCode) return error.statusCode;
388
+
389
+ const code = classifyError(error);
390
+
391
+ switch (code) {
392
+ case ERROR_CODES.VALIDATION_ERROR:
393
+ return 400;
394
+ case ERROR_CODES.AUTHENTICATION_ERROR:
395
+ return 401;
396
+ case ERROR_CODES.AUTHORIZATION_ERROR:
397
+ return 403;
398
+ case ERROR_CODES.NOT_FOUND:
399
+ return 404;
400
+ case ERROR_CODES.RATE_LIMITED:
401
+ return 429;
402
+ default:
403
+ return 500;
404
+ }
405
+ },
406
+
407
+ /**
408
+ * Wrap an async handler with error sanitization
409
+ * @param {Function} handler - Async handler function
410
+ * @returns {Function} Wrapped handler
411
+ */
412
+ wrap(handler) {
413
+ return async (req, res, next) => {
414
+ try {
415
+ await handler(req, res, next);
416
+ } catch (error) {
417
+ const sanitized = this.sanitize(error, {
418
+ requestId: req.id || req.headers['x-request-id'],
419
+ });
420
+ const statusCode = this.getStatusCode(error);
421
+ res.status(statusCode).json(sanitized);
422
+ }
423
+ };
424
+ },
425
+ };
426
+ }