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.
- package/package.json +1 -1
- package/server/index.js +229 -14
- package/server/lib/compliance/control-mapper.js +401 -0
- package/server/lib/compliance/control-mapper.test.js +117 -0
- package/server/lib/compliance/evidence-linker.js +296 -0
- package/server/lib/compliance/evidence-linker.test.js +121 -0
- package/server/lib/compliance/gdpr-checklist.js +416 -0
- package/server/lib/compliance/gdpr-checklist.test.js +131 -0
- package/server/lib/compliance/hipaa-checklist.js +277 -0
- package/server/lib/compliance/hipaa-checklist.test.js +101 -0
- package/server/lib/compliance/iso27001-checklist.js +287 -0
- package/server/lib/compliance/iso27001-checklist.test.js +99 -0
- package/server/lib/compliance/multi-framework-reporter.js +284 -0
- package/server/lib/compliance/multi-framework-reporter.test.js +127 -0
- package/server/lib/compliance/pci-dss-checklist.js +214 -0
- package/server/lib/compliance/pci-dss-checklist.test.js +95 -0
- package/server/lib/compliance/trust-centre.js +187 -0
- package/server/lib/compliance/trust-centre.test.js +93 -0
- package/server/lib/dashboard/api-server.js +155 -0
- package/server/lib/dashboard/api-server.test.js +155 -0
- package/server/lib/dashboard/health-api.js +199 -0
- package/server/lib/dashboard/health-api.test.js +122 -0
- package/server/lib/dashboard/notes-api.js +234 -0
- package/server/lib/dashboard/notes-api.test.js +134 -0
- package/server/lib/dashboard/router-api.js +176 -0
- package/server/lib/dashboard/router-api.test.js +132 -0
- package/server/lib/dashboard/tasks-api.js +289 -0
- package/server/lib/dashboard/tasks-api.test.js +161 -0
- package/server/lib/dashboard/tlc-introspection.js +197 -0
- package/server/lib/dashboard/tlc-introspection.test.js +138 -0
- package/server/lib/dashboard/version-api.js +222 -0
- package/server/lib/dashboard/version-api.test.js +112 -0
- package/server/lib/dashboard/websocket-server.js +104 -0
- package/server/lib/dashboard/websocket-server.test.js +118 -0
- package/server/lib/deploy/branch-classifier.js +163 -0
- package/server/lib/deploy/branch-classifier.test.js +164 -0
- package/server/lib/deploy/deployment-approval.js +299 -0
- package/server/lib/deploy/deployment-approval.test.js +296 -0
- package/server/lib/deploy/deployment-audit.js +374 -0
- package/server/lib/deploy/deployment-audit.test.js +307 -0
- package/server/lib/deploy/deployment-executor.js +335 -0
- package/server/lib/deploy/deployment-executor.test.js +329 -0
- package/server/lib/deploy/deployment-rules.js +163 -0
- package/server/lib/deploy/deployment-rules.test.js +188 -0
- package/server/lib/deploy/rollback-manager.js +379 -0
- package/server/lib/deploy/rollback-manager.test.js +321 -0
- package/server/lib/deploy/security-gates.js +236 -0
- package/server/lib/deploy/security-gates.test.js +222 -0
- package/server/lib/k8s/gitops-config.js +188 -0
- package/server/lib/k8s/gitops-config.test.js +59 -0
- package/server/lib/k8s/helm-generator.js +196 -0
- package/server/lib/k8s/helm-generator.test.js +59 -0
- package/server/lib/k8s/kustomize-generator.js +176 -0
- package/server/lib/k8s/kustomize-generator.test.js +58 -0
- package/server/lib/k8s/network-policy.js +114 -0
- package/server/lib/k8s/network-policy.test.js +53 -0
- package/server/lib/k8s/pod-security.js +114 -0
- package/server/lib/k8s/pod-security.test.js +55 -0
- package/server/lib/k8s/rbac-generator.js +132 -0
- package/server/lib/k8s/rbac-generator.test.js +57 -0
- package/server/lib/k8s/resource-manager.js +172 -0
- package/server/lib/k8s/resource-manager.test.js +60 -0
- package/server/lib/k8s/secrets-encryption.js +168 -0
- package/server/lib/k8s/secrets-encryption.test.js +49 -0
- package/server/lib/monitoring/alert-manager.js +238 -0
- package/server/lib/monitoring/alert-manager.test.js +106 -0
- package/server/lib/monitoring/health-check.js +226 -0
- package/server/lib/monitoring/health-check.test.js +176 -0
- package/server/lib/monitoring/incident-manager.js +230 -0
- package/server/lib/monitoring/incident-manager.test.js +98 -0
- package/server/lib/monitoring/log-aggregator.js +147 -0
- package/server/lib/monitoring/log-aggregator.test.js +89 -0
- package/server/lib/monitoring/metrics-collector.js +337 -0
- package/server/lib/monitoring/metrics-collector.test.js +172 -0
- package/server/lib/monitoring/status-page.js +214 -0
- package/server/lib/monitoring/status-page.test.js +105 -0
- package/server/lib/monitoring/uptime-monitor.js +194 -0
- package/server/lib/monitoring/uptime-monitor.test.js +109 -0
- package/server/lib/network/fail2ban-config.js +294 -0
- package/server/lib/network/fail2ban-config.test.js +275 -0
- package/server/lib/network/firewall-manager.js +252 -0
- package/server/lib/network/firewall-manager.test.js +254 -0
- package/server/lib/network/geoip-filter.js +282 -0
- package/server/lib/network/geoip-filter.test.js +264 -0
- package/server/lib/network/rate-limiter.js +229 -0
- package/server/lib/network/rate-limiter.test.js +293 -0
- package/server/lib/network/request-validator.js +351 -0
- package/server/lib/network/request-validator.test.js +345 -0
- package/server/lib/network/security-headers.js +251 -0
- package/server/lib/network/security-headers.test.js +283 -0
- package/server/lib/network/tls-config.js +210 -0
- package/server/lib/network/tls-config.test.js +248 -0
- package/server/lib/security/auth-security.js +369 -0
- package/server/lib/security/auth-security.test.js +448 -0
- package/server/lib/security/cis-benchmark.js +152 -0
- package/server/lib/security/cis-benchmark.test.js +137 -0
- package/server/lib/security/compose-templates.js +312 -0
- package/server/lib/security/compose-templates.test.js +229 -0
- package/server/lib/security/container-runtime.js +456 -0
- package/server/lib/security/container-runtime.test.js +503 -0
- package/server/lib/security/cors-validator.js +278 -0
- package/server/lib/security/cors-validator.test.js +310 -0
- package/server/lib/security/crypto-utils.js +253 -0
- package/server/lib/security/crypto-utils.test.js +409 -0
- package/server/lib/security/dockerfile-linter.js +459 -0
- package/server/lib/security/dockerfile-linter.test.js +483 -0
- package/server/lib/security/dockerfile-templates.js +278 -0
- package/server/lib/security/dockerfile-templates.test.js +164 -0
- package/server/lib/security/error-sanitizer.js +426 -0
- package/server/lib/security/error-sanitizer.test.js +331 -0
- package/server/lib/security/headers-generator.js +368 -0
- package/server/lib/security/headers-generator.test.js +398 -0
- package/server/lib/security/image-scanner.js +83 -0
- package/server/lib/security/image-scanner.test.js +106 -0
- package/server/lib/security/input-validator.js +352 -0
- package/server/lib/security/input-validator.test.js +330 -0
- package/server/lib/security/network-policy.js +174 -0
- package/server/lib/security/network-policy.test.js +164 -0
- package/server/lib/security/output-encoder.js +237 -0
- package/server/lib/security/output-encoder.test.js +276 -0
- package/server/lib/security/path-validator.js +359 -0
- package/server/lib/security/path-validator.test.js +293 -0
- package/server/lib/security/query-builder.js +421 -0
- package/server/lib/security/query-builder.test.js +318 -0
- package/server/lib/security/secret-detector.js +290 -0
- package/server/lib/security/secret-detector.test.js +354 -0
- package/server/lib/security/secrets-validator.js +137 -0
- package/server/lib/security/secrets-validator.test.js +120 -0
- package/server/lib/security-testing/dast-runner.js +154 -0
- package/server/lib/security-testing/dast-runner.test.js +62 -0
- package/server/lib/security-testing/dependency-scanner.js +172 -0
- package/server/lib/security-testing/dependency-scanner.test.js +64 -0
- package/server/lib/security-testing/pentest-runner.js +230 -0
- package/server/lib/security-testing/pentest-runner.test.js +60 -0
- package/server/lib/security-testing/sast-runner.js +136 -0
- package/server/lib/security-testing/sast-runner.test.js +62 -0
- package/server/lib/security-testing/secret-scanner.js +153 -0
- package/server/lib/security-testing/secret-scanner.test.js +66 -0
- package/server/lib/security-testing/security-gate.js +216 -0
- package/server/lib/security-testing/security-gate.test.js +115 -0
- package/server/lib/security-testing/security-reporter.js +303 -0
- package/server/lib/security-testing/security-reporter.test.js +114 -0
- package/server/lib/standards/audit-checker.js +546 -0
- package/server/lib/standards/audit-checker.test.js +415 -0
- package/server/lib/standards/cleanup-executor.js +452 -0
- package/server/lib/standards/cleanup-executor.test.js +293 -0
- package/server/lib/standards/refactor-stepper.js +425 -0
- package/server/lib/standards/refactor-stepper.test.js +298 -0
- package/server/lib/standards/standards-injector.js +167 -0
- package/server/lib/standards/standards-injector.test.js +232 -0
- package/server/lib/user-management.test.js +284 -0
- package/server/lib/vps/backup-manager.js +157 -0
- package/server/lib/vps/backup-manager.test.js +59 -0
- package/server/lib/vps/caddy-config.js +159 -0
- package/server/lib/vps/caddy-config.test.js +48 -0
- package/server/lib/vps/compose-orchestrator.js +219 -0
- package/server/lib/vps/compose-orchestrator.test.js +50 -0
- package/server/lib/vps/database-config.js +208 -0
- package/server/lib/vps/database-config.test.js +47 -0
- package/server/lib/vps/deploy-script.js +211 -0
- package/server/lib/vps/deploy-script.test.js +53 -0
- package/server/lib/vps/secrets-manager.js +148 -0
- package/server/lib/vps/secrets-manager.test.js +58 -0
- package/server/lib/vps/server-hardening.js +174 -0
- package/server/lib/vps/server-hardening.test.js +70 -0
- package/server/package-lock.json +19 -0
- package/server/package.json +1 -0
- package/server/templates/CLAUDE.md +37 -0
- 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
|
+
}
|