i18ntk 2.0.4 → 2.2.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.
Files changed (57) hide show
  1. package/README.md +38 -60
  2. package/main/i18ntk-analyze.js +49 -44
  3. package/main/i18ntk-complete.js +75 -74
  4. package/main/i18ntk-fixer.js +3 -3
  5. package/main/i18ntk-init.js +5 -5
  6. package/main/i18ntk-scanner.js +2 -2
  7. package/main/i18ntk-sizing.js +35 -35
  8. package/main/i18ntk-summary.js +4 -4
  9. package/main/i18ntk-ui.js +54 -8
  10. package/main/i18ntk-usage.js +14 -14
  11. package/main/i18ntk-validate.js +6 -5
  12. package/main/manage/commands/AnalyzeCommand.js +40 -35
  13. package/main/manage/commands/FixerCommand.js +2 -2
  14. package/main/manage/commands/ScannerCommand.js +2 -2
  15. package/main/manage/commands/ValidateCommand.js +9 -9
  16. package/main/manage/index.js +147 -75
  17. package/main/manage/managers/LanguageMenu.js +7 -2
  18. package/main/manage/services/UsageService.js +7 -7
  19. package/package.json +269 -290
  20. package/settings/settings-cli.js +3 -3
  21. package/ui-locales/de.json +161 -166
  22. package/ui-locales/en.json +13 -18
  23. package/ui-locales/es.json +171 -184
  24. package/ui-locales/fr.json +155 -161
  25. package/ui-locales/ja.json +192 -243
  26. package/ui-locales/ru.json +145 -196
  27. package/ui-locales/zh.json +179 -185
  28. package/utils/cli-helper.js +26 -98
  29. package/utils/extractors/regex.js +39 -12
  30. package/utils/i18n-helper.js +88 -40
  31. package/{scripts → utils}/locale-optimizer.js +61 -60
  32. package/utils/security-check-improved.js +16 -13
  33. package/utils/security.js +6 -4
  34. package/main/i18ntk-go.js +0 -283
  35. package/main/i18ntk-java.js +0 -380
  36. package/main/i18ntk-js.js +0 -512
  37. package/main/i18ntk-manage.js +0 -1694
  38. package/main/i18ntk-php.js +0 -462
  39. package/main/i18ntk-py.js +0 -379
  40. package/main/i18ntk-settings.js +0 -23
  41. package/main/manage/index-fixed.js +0 -1447
  42. package/main/manage/services/ConfigurationService-fixed.js +0 -449
  43. package/scripts/build-lite.js +0 -279
  44. package/scripts/deprecate-versions.js +0 -317
  45. package/scripts/export-translations.js +0 -84
  46. package/scripts/fix-all-i18n.js +0 -215
  47. package/scripts/fix-and-purify-i18n.js +0 -213
  48. package/scripts/fix-locale-control-chars.js +0 -110
  49. package/scripts/lint-locales.js +0 -80
  50. package/scripts/prepublish.js +0 -348
  51. package/scripts/security-check.js +0 -117
  52. package/scripts/sync-translations.js +0 -151
  53. package/scripts/sync-ui-locales.js +0 -20
  54. package/scripts/validate-all-translations.js +0 -139
  55. package/scripts/verify-deprecations.js +0 -157
  56. package/scripts/verify-translations.js +0 -63
  57. package/utils/security-fixed.js +0 -607
@@ -1,607 +0,0 @@
1
- const path = require('path');
2
- const fs = require('fs');
3
- const crypto = require('crypto');
4
-
5
-
6
- // Lazy load configManager to avoid circular dependency
7
- let configManager;
8
- function getConfigManager() {
9
- if (!configManager) {
10
- try {
11
- configManager = require('./config-manager');
12
- } catch (error) {
13
- // Return null if config-manager can't be loaded
14
- return null;
15
- }
16
- }
17
- return configManager;
18
- }
19
-
20
- // Lazy load i18n to prevent initialization race conditions
21
- let i18n;
22
- function getI18n() {
23
- if (!i18n) {
24
- try {
25
- i18n = require('./i18n-helper');
26
- } catch (error) {
27
- // Fallback to simple identity function if i18n fails to load
28
- console.warn('i18n-helper not available, using fallback messages');
29
- return { t: (key, params = {}) => key };
30
- }
31
- }
32
- return i18n;
33
- }
34
-
35
- /**
36
- * Security utility module for i18nTK
37
- * Provides secure file operations, path validation, and input sanitization
38
- * to prevent path traversal, code injection, and other security vulnerabilities
39
- */
40
- console.log('🔍 DEBUG: SecurityUtils class loaded successfully');
41
- class SecurityUtils {
42
-
43
- // Static properties for operation tracking
44
- static _operationStack = new Set();
45
- static _logging = false;
46
-
47
- constructor() {
48
- // Instance constructor - static properties are already initialized
49
- }
50
-
51
- /**
52
- * Timeout wrapper for synchronous operations to prevent hanging
53
- * @param {Function} operation - The synchronous operation to wrap
54
- * @param {number} timeoutMs - Timeout in milliseconds
55
- * @param {string} operationName - Name of the operation for logging
56
- * @returns {*} - Operation result or null if timeout/error
57
- */
58
- static withTimeoutSync(operation, timeoutMs = 5000, operationName = 'operation') {
59
- // Track recursion to prevent infinite loops
60
- if (!SecurityUtils._operationStack) {
61
- SecurityUtils._operationStack = new Set();
62
- }
63
-
64
- if (SecurityUtils._operationStack.has(operationName)) {
65
- const i18n = getI18n();
66
- SecurityUtils.logSecurityEvent(i18n.t('security.recursion_detected', { operation: operationName }), 'error');
67
- return null;
68
- }
69
-
70
- SecurityUtils._operationStack.add(operationName);
71
-
72
- try {
73
- // Simple timeout using setTimeout for synchronous operations
74
- let result = null;
75
- let hasResult = false;
76
- let timeoutId = null;
77
-
78
- const timeoutPromise = new Promise((resolve) => {
79
- timeoutId = setTimeout(() => {
80
- if (!hasResult) {
81
- const i18n = getI18n();
82
- SecurityUtils.logSecurityEvent(i18n.t('security.operation_timeout', { operation: operationName }), 'warning');
83
- resolve(null);
84
- }
85
- }, timeoutMs);
86
- });
87
-
88
- // Execute operation synchronously
89
- result = operation();
90
- hasResult = true;
91
-
92
- if (timeoutId) {
93
- clearTimeout(timeoutId);
94
- }
95
-
96
- return result;
97
- } catch (error) {
98
- const i18n = getI18n();
99
- console.warn(i18n.t('security.operation_error', { operation: operationName, error: error.message }));
100
- return null;
101
- } finally {
102
- SecurityUtils._operationStack.delete(operationName);
103
- }
104
- }
105
-
106
- /**
107
- * Logs security events for monitoring
108
- * @param {string} event - Security event description
109
- * @param {string} level - Log level (info, warn, error)
110
- * @param {object} details - Additional details
111
- */
112
- static logSecurityEvent(event, level = 'info', details = {}) {
113
- // Prevent recursive logging which can occur during configuration loading
114
- if (SecurityUtils._logging) {
115
- return;
116
- }
117
-
118
- SecurityUtils._logging = true;
119
- try {
120
- const cfg = getConfigManager()?.getConfig?.() || {};
121
- const envLevel = (process.env.SECURITY_LOG_LEVEL || process.env.I18NTK_SECURITY_LOG_LEVEL || '').toLowerCase();
122
- const configLevel = (cfg.security?.logLevel || cfg.security?.audit?.logLevel || '').toLowerCase();
123
- const currentLevel = envLevel || configLevel || 'warn';
124
-
125
- const levels = { error: 0, warn: 1, warning: 1, info: 2 };
126
- const messageLevel = levels[level.toLowerCase()] ?? 2;
127
- const allowedLevel = levels[currentLevel] ?? 1;
128
- if (messageLevel > allowedLevel) {
129
- return;
130
- }
131
-
132
- const timestamp = new Date().toISOString();
133
- const logEntry = {
134
- timestamp,
135
- level,
136
- event,
137
- details: {
138
- ...details,
139
- pid: process.pid,
140
- nodeVersion: process.version
141
- }
142
- };
143
-
144
- const message = `[SECURITY ${level.toUpperCase()}] ${timestamp}: ${event}`;
145
- if (level === 'error') {
146
- console.error(message, details);
147
- } else if (level === 'warn' || level === 'warning') {
148
- console.warn(message, details);
149
- } else {
150
- console.log(message, details);
151
- }
152
- } finally {
153
- SecurityUtils._logging = false;
154
- }
155
- }
156
-
157
- // Add other static methods here...
158
- static validatePath(filePath, basePath = process.cwd(), verbose = false) {
159
- const i18n = getI18n();
160
- const useI18n = i18n && i18n.isInitialized && typeof i18n.t === 'function';
161
-
162
- try {
163
- if (!filePath || typeof filePath !== 'string') {
164
- const message = useI18n
165
- ? i18n.t('security.pathValidationFailed')
166
- : 'Path validation failed';
167
- const reason = useI18n
168
- ? i18n.t('security.invalidInputType')
169
- : 'Invalid input type';
170
- SecurityUtils.logSecurityEvent(message, 'error', {
171
- inputPath: filePath,
172
- reason
173
- });
174
- return null;
175
- }
176
-
177
- // Check for obvious dangerous patterns first
178
- if (!SecurityUtils.isSafePath(filePath)) {
179
- const message = useI18n
180
- ? i18n.t('security.pathTraversalAttempt')
181
- : 'Path traversal attempt';
182
- SecurityUtils.logSecurityEvent(message, 'warning', {
183
- inputPath: filePath,
184
- reason: 'Contains dangerous patterns'
185
- });
186
- return null;
187
- }
188
-
189
- // Resolve base and target paths
190
- const base = fs.realpathSync(basePath);
191
- const resolvedPath = path.resolve(base, filePath);
192
-
193
- // Resolve symlinks if the path exists
194
- let finalPath = resolvedPath;
195
- try {
196
- finalPath = fs.realpathSync(resolvedPath);
197
- } catch {
198
- // If the path doesn't exist yet, fall back to the resolved path
199
- }
200
-
201
- // Check for actual path traversal (going outside the base directory)
202
- const relativePath = path.relative(base, finalPath);
203
- if (relativePath.startsWith('..')) {
204
- const message = useI18n
205
- ? i18n.t('security.pathTraversalAttempt')
206
- : 'Path traversal attempt';
207
- SecurityUtils.logSecurityEvent(message, 'warning', {
208
- inputPath: filePath,
209
- resolvedPath: finalPath,
210
- basePath: base,
211
- relativePath: relativePath
212
- });
213
- return null;
214
- }
215
-
216
- // Allow absolute paths that resolve within the project structure
217
- // The isSafePath check above already filtered out dangerous absolute paths
218
-
219
- if (verbose) {
220
- const successMsg = useI18n
221
- ? i18n.t('security.pathValidated')
222
- : 'Path validated';
223
- SecurityUtils.logSecurityEvent(successMsg, 'info', {
224
- inputPath: filePath,
225
- resolvedPath: finalPath
226
- });
227
- }
228
- return finalPath;
229
- } catch (error) {
230
- const message = useI18n
231
- ? i18n.t('security.pathValidationError')
232
- : 'Path validation error';
233
- SecurityUtils.logSecurityEvent(message, 'error', {
234
- inputPath: filePath,
235
- error: error.message
236
- });
237
- return null;
238
- }
239
- }
240
-
241
- static safeExistsSync(filePath, basePath, timeoutMs = 3000) {
242
- return this.withTimeoutSync(() => {
243
- const validatedPath = this.validatePath(filePath, basePath);
244
- if (!validatedPath) {
245
- return false;
246
- }
247
- try {
248
- return fs.existsSync(validatedPath);
249
- } catch (error) {
250
- return false;
251
- }
252
- }, timeoutMs, 'safeExistsSync');
253
- }
254
-
255
- static safeReadFileSync(filePath, basePath, encoding = 'utf8') {
256
- const validatedPath = this.validatePath(filePath, basePath);
257
- if (!validatedPath) {
258
- return null;
259
- }
260
- const i18n = getI18n();
261
- try {
262
- // Check if file exists and is readable
263
- fs.accessSync(validatedPath, fs.constants.R_OK);
264
-
265
- // Read file with size limit (10MB max)
266
- const stats = fs.statSync(validatedPath);
267
- if (stats.size > 10 * 1024 * 1024) {
268
- console.warn(i18n.t('security.file_too_large', { filePath: validatedPath }));
269
- return null;
270
- }
271
-
272
- return fs.readFileSync(validatedPath, encoding);
273
- } catch (error) {
274
- console.warn(i18n.t('security.file_read_error', { errorMessage: error.message }));
275
- return null;
276
- }
277
- }
278
-
279
- static safeWriteFileSync(filePath, content, basePath, encoding = 'utf8') {
280
- const validatedPath = this.validatePath(filePath, basePath);
281
- if (!validatedPath) {
282
- return false;
283
- }
284
-
285
- try {
286
- // Validate content size (10MB max)
287
- if (typeof content === 'string' && content.length > 10 * 1024 * 1024) {
288
- const i18n = getI18n();
289
- console.warn(i18n.t('security.content_too_large_for_file', { filePath: validatedPath }));
290
- return false;
291
- }
292
-
293
- // Ensure directory exists
294
- const dir = path.dirname(validatedPath);
295
- fs.mkdirSync(dir, { recursive: true });
296
-
297
- // Write file with proper permissions
298
- fs.writeFileSync(validatedPath, content, { encoding, mode: 0o644 });
299
- return true;
300
- } catch (error) {
301
- const i18n = getI18n();
302
- console.warn(i18n.t('security.file_write_error', { errorMessage: error.message }));
303
- return false;
304
- }
305
- }
306
-
307
- /**
308
- * Async compatibility wrapper for safeReadFileSync.
309
- * @param {string} filePath
310
- * @param {string} basePath
311
- * @param {string} encoding
312
- * @returns {Promise<string|null>}
313
- */
314
- static async safeReadFile(filePath, basePath, encoding = 'utf8') {
315
- return this.safeReadFileSync(filePath, basePath, encoding);
316
- }
317
-
318
- /**
319
- * Async compatibility wrapper for safeWriteFileSync.
320
- * @param {string} filePath
321
- * @param {string|Buffer} content
322
- * @param {string} basePath
323
- * @param {string} encoding
324
- * @returns {Promise<boolean>}
325
- */
326
- static async safeWriteFile(filePath, content, basePath, encoding = 'utf8') {
327
- return this.safeWriteFileSync(filePath, content, basePath, encoding);
328
- }
329
-
330
- /**
331
- * Safely parse JSON content.
332
- * Accepts both raw JSON strings and already-parsed objects.
333
- * @param {string|object} input - JSON string or object
334
- * @param {*} fallback - Value to return on parse error
335
- * @returns {*}
336
- */
337
- static safeParseJSON(input, fallback = null) {
338
- if (input === null || input === undefined) {
339
- return fallback;
340
- }
341
-
342
- if (typeof input === 'object') {
343
- return input;
344
- }
345
-
346
- if (typeof input !== 'string') {
347
- return fallback;
348
- }
349
-
350
- const trimmed = input.trim();
351
- if (!trimmed) {
352
- return fallback;
353
- }
354
-
355
- try {
356
- const normalized = trimmed.charCodeAt(0) === 0xFEFF ? trimmed.slice(1) : trimmed;
357
- return JSON.parse(normalized);
358
- } catch (error) {
359
- console.warn(`Invalid JSON content: ${error.message}`);
360
- return fallback;
361
- }
362
- }
363
-
364
- static sanitizeInput(input, options = {}) {
365
- if (!input || typeof input !== 'string') {
366
- return '';
367
- }
368
-
369
- const {
370
- allowedChars = /^[a-zA-Z0-9\s\-_\.\,\!\?\(\)\[\]\{\}\:\;"'\/\\]+$/,
371
- maxLength = 1000,
372
- removeHTML = true,
373
- removeScripts = true
374
- } = options;
375
-
376
- let sanitized = input.trim();
377
-
378
- // Limit length
379
- if (sanitized.length > maxLength) {
380
- sanitized = sanitized.substring(0, maxLength);
381
- }
382
-
383
- // Remove HTML tags if requested
384
- if (removeHTML) {
385
- sanitized = sanitized.replace(/<[^>]*>/g, '');
386
- }
387
-
388
- // Remove script-like content
389
- if (removeScripts) {
390
- sanitized = sanitized.replace(/javascript:/gi, '');
391
- sanitized = sanitized.replace(/on\w+\s*=/gi, '');
392
- sanitized = sanitized.replace(/eval\s*\(/gi, '');
393
- sanitized = sanitized.replace(/function\s*\(/gi, '');
394
- }
395
-
396
- // Check against allowed characters - suppress warnings for normal operations
397
- if (!allowedChars.test(sanitized)) {
398
- // Skip warning for common file path characters and reduce verbosity
399
- const isFilePath = sanitized.includes('/') || sanitized.includes('\\') || sanitized.includes('.');
400
- const isCommonContent = sanitized.length < 1000 && !sanitized.includes('<script');
401
- if (!isFilePath && !isCommonContent) {
402
- const i18n = getI18n();
403
- console.warn(i18n.t('security.inputDisallowedCharacters'));
404
- }
405
- // Allow more characters for file paths and content
406
- sanitized = sanitized.replace(/[^a-zA-Z0-9\s\-_\.\,\!\?\(\)\[\]\{\}\:\;"'\/\\]/g, '');
407
- }
408
-
409
- return sanitized;
410
- }
411
-
412
- static generateHash(content) {
413
- return crypto.createHash('sha256').update(content).digest('hex');
414
- }
415
-
416
- static isSafePath(filePath) {
417
- if (!filePath || typeof filePath !== 'string') {
418
- return false;
419
- }
420
-
421
- // Check for dangerous patterns
422
- const dangerousPatterns = [
423
- /\.\./, // Parent directory traversal
424
- /^\//, // Absolute path (Unix)
425
- /^[A-Z]:\\/, // Absolute path (Windows)
426
- /~/, // Home directory
427
- /\$\{/, // Variable expansion
428
- /`/, // Command substitution
429
- /\|/, // Pipe
430
- /;/, // Command separator
431
- /&/, // Background process
432
- />/, // Redirect
433
- /</ // Redirect
434
- ];
435
-
436
- return !dangerousPatterns.some(pattern => pattern.test(filePath));
437
- }
438
- static validateConfig(config) {
439
- if (!config || typeof config !== 'object') {
440
- SecurityUtils.logSecurityEvent('Invalid configuration object provided', 'error', {
441
- configType: typeof config
442
- });
443
- return {};
444
- }
445
-
446
- const sanitized = { ...config };
447
- const i18n = getI18n();
448
-
449
- // Define allowed configuration properties
450
- const allowedProperties = new Set([
451
- // Core directories and paths
452
- 'projectRoot', 'sourceDir', 'i18nDir', 'outputDir', 'backupDir', 'tempDir', 'cacheDir', 'configDir',
453
- // Language settings
454
- 'sourceLanguage', 'uiLanguage', 'language', 'defaultLanguages', 'supportedLanguages',
455
- // Translation markers and content
456
- 'notTranslatedMarker', 'notTranslatedMarkers', 'translatedMarker', 'translatedMarkers',
457
- // File handling
458
- 'supportedExtensions', 'excludeFiles', 'excludeDirs', 'includeFiles', 'includeDirs',
459
- // Operational settings
460
- 'strictMode', 'debug', 'displayPaths', 'version', 'scriptDirectories',
461
- // Framework and processing
462
- 'framework', 'processing', 'performance', 'advanced',
463
- // UI and theme settings
464
- 'theme', 'ui', 'setup', 'reports', 'display', 'interface',
465
- // Security and settings
466
- 'security', 'settings', 'preferences', 'config', 'configuration',
467
- // Additional common properties
468
- 'autoSave', 'autoBackup', 'validateOnSave', 'showWarnings', 'verbose',
469
- 'timeout', 'retries', 'batchSize', 'maxConcurrency', 'cacheEnabled'
470
- ]);
471
-
472
- // Remove unknown properties
473
- Object.keys(sanitized).forEach(key => {
474
- if (!allowedProperties.has(key)) {
475
- // Only log warnings for properties that might be security risks
476
- const value = sanitized[key];
477
- const isSuspicious = typeof value === 'string' &&
478
- (value.includes('..') || value.includes('/') || value.includes('\\') ||
479
- value.includes('$') || value.includes('`') || value.includes('|') ||
480
- value.includes(';') || value.includes('&'));
481
-
482
- if (isSuspicious) {
483
- SecurityUtils.logSecurityEvent('Removing potentially suspicious configuration property', 'warn', {
484
- property: key,
485
- value: sanitized[key]
486
- });
487
- } else {
488
- // Use info level for normal unknown properties to reduce noise
489
- SecurityUtils.logSecurityEvent('Removing unknown configuration property', 'info', {
490
- property: key,
491
- value: sanitized[key]
492
- });
493
- }
494
- delete sanitized[key];
495
- }
496
- });
497
-
498
- // Validate and sanitize path properties
499
- const pathProperties = ['projectRoot', 'sourceDir', 'i18nDir', 'outputDir', 'backupDir', 'tempDir', 'cacheDir', 'configDir'];
500
-
501
- pathProperties.forEach(prop => {
502
- if (sanitized[prop] && typeof sanitized[prop] === 'string') {
503
- // Check for dangerous patterns
504
- if (!SecurityUtils.isSafePath(sanitized[prop])) {
505
- SecurityUtils.logSecurityEvent('Path validation failed for configuration property', 'error', {
506
- property: prop,
507
- originalPath: sanitized[prop]
508
- });
509
-
510
- // Attempt to sanitize the path by removing dangerous patterns
511
- let sanitizedPath = sanitized[prop];
512
-
513
- // Remove parent directory references (path traversal)
514
- sanitizedPath = sanitizedPath.replace(/\.\.[\/\\]/g, '');
515
-
516
- // Remove shell metacharacters and dangerous patterns
517
- sanitizedPath = sanitizedPath.replace(/[|;&$`{}()[\]<>?]/g, '');
518
-
519
- // Only remove absolute path indicators if they're suspicious
520
- // Allow legitimate Windows drive letters (C:\, D:\, etc.) but remove suspicious ones
521
- if (sanitizedPath.match(/^[A-Z]:[\/\\]/)) {
522
- // Check if this looks like a legitimate Windows path by ensuring it doesn't contain
523
- // suspicious patterns after the drive letter
524
- const afterDrive = sanitizedPath.substring(3); // Everything after "C:\"
525
- if (afterDrive.includes('..') || afterDrive.match(/[|;&$`{}()[\]<>?]/)) {
526
- // Remove the drive letter if the rest of the path is suspicious
527
- sanitizedPath = sanitizedPath.replace(/^[A-Z]:[\/\\]/, '');
528
- }
529
- // If the path looks legitimate, keep the drive letter
530
- } else {
531
- // For non-Windows paths, remove leading slashes as before
532
- sanitizedPath = sanitizedPath.replace(/^[\/\\]/, '');
533
- }
534
-
535
- if (sanitizedPath !== sanitized[prop]) {
536
- // Only warn if significant changes were made (not just removing drive letters for legitimate paths)
537
- const significantChange = sanitizedPath.length < sanitized[prop].length * 0.8 ||
538
- sanitizedPath.replace(/[\/\\]/g, '') !== sanitized[prop].replace(/^[A-Z]:[\/\\]/, '').replace(/[\/\\]/g, '');
539
-
540
- if (significantChange) {
541
- SecurityUtils.logSecurityEvent('Path sanitized for configuration property', 'warn', {
542
- property: prop,
543
- originalPath: sanitized[prop],
544
- sanitizedPath: sanitizedPath
545
- });
546
- } else {
547
- SecurityUtils.logSecurityEvent('Path normalized for configuration property', 'info', {
548
- property: prop,
549
- originalPath: sanitized[prop],
550
- sanitizedPath: sanitizedPath
551
- });
552
- }
553
- sanitized[prop] = sanitizedPath;
554
- }
555
- }
556
- }
557
- });
558
-
559
- // Validate security settings
560
- if (sanitized.security) {
561
- const security = sanitized.security;
562
-
563
- // Validate session timeout (should be reasonable)
564
- if (security.sessionTimeout && (typeof security.sessionTimeout !== 'number' || security.sessionTimeout < 60000 || security.sessionTimeout > 86400000)) {
565
- SecurityUtils.logSecurityEvent('Invalid session timeout in security configuration', 'warn', {
566
- sessionTimeout: security.sessionTimeout
567
- });
568
- security.sessionTimeout = 1800000; // Default to 30 minutes
569
- }
570
-
571
- // Validate max failed attempts
572
- if (security.maxFailedAttempts && (typeof security.maxFailedAttempts !== 'number' || security.maxFailedAttempts < 1 || security.maxFailedAttempts > 10)) {
573
- SecurityUtils.logSecurityEvent('Invalid max failed attempts in security configuration', 'warn', {
574
- maxFailedAttempts: security.maxFailedAttempts
575
- });
576
- security.maxFailedAttempts = 3; // Default to 3 attempts
577
- }
578
- }
579
-
580
- // Validate language settings
581
- if (sanitized.sourceLanguage && typeof sanitized.sourceLanguage === 'string') {
582
- // Sanitize language code (only allow alphanumeric, hyphens, underscores)
583
- sanitized.sourceLanguage = sanitized.sourceLanguage.replace(/[^a-zA-Z0-9\-_]/g, '');
584
- }
585
-
586
- if (sanitized.uiLanguage && typeof sanitized.uiLanguage === 'string') {
587
- sanitized.uiLanguage = sanitized.uiLanguage.replace(/[^a-zA-Z0-9\-_]/g, '');
588
- }
589
-
590
- // Validate default languages array
591
- if (sanitized.defaultLanguages && Array.isArray(sanitized.defaultLanguages)) {
592
- sanitized.defaultLanguages = sanitized.defaultLanguages
593
- .filter(lang => typeof lang === 'string')
594
- .map(lang => lang.replace(/[^a-zA-Z0-9\-_]/g, ''))
595
- .filter(lang => lang.length > 0);
596
- }
597
-
598
- SecurityUtils.logSecurityEvent('Configuration validation completed', 'info', {
599
- propertiesCount: Object.keys(sanitized).length,
600
- sanitizedPaths: pathProperties.filter(prop => sanitized[prop]).length
601
- });
602
-
603
- return sanitized;
604
- }
605
- }
606
-
607
- module.exports = SecurityUtils;