i18ntk 1.10.1 → 2.0.2

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 (110) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +141 -1185
  3. package/main/i18ntk-analyze.js +149 -133
  4. package/main/i18ntk-backup-class.js +420 -0
  5. package/main/i18ntk-backup.js +4 -4
  6. package/main/i18ntk-complete.js +90 -65
  7. package/main/i18ntk-doctor.js +123 -103
  8. package/main/i18ntk-fixer.js +61 -725
  9. package/main/i18ntk-go.js +14 -15
  10. package/main/i18ntk-init.js +76 -25
  11. package/main/i18ntk-java.js +27 -32
  12. package/main/i18ntk-js.js +70 -68
  13. package/main/i18ntk-manage.js +128 -29
  14. package/main/i18ntk-php.js +75 -75
  15. package/main/i18ntk-py.js +55 -56
  16. package/main/i18ntk-scanner.js +59 -57
  17. package/main/i18ntk-setup.js +10 -396
  18. package/main/i18ntk-sizing.js +46 -40
  19. package/main/i18ntk-summary.js +21 -18
  20. package/main/i18ntk-ui.js +11 -10
  21. package/main/i18ntk-usage.js +55 -19
  22. package/main/i18ntk-validate.js +13 -13
  23. package/main/manage/commands/AnalyzeCommand.js +1124 -0
  24. package/main/manage/commands/BackupCommand.js +62 -0
  25. package/main/manage/commands/CommandRouter.js +295 -0
  26. package/main/manage/commands/CompleteCommand.js +61 -0
  27. package/main/manage/commands/DoctorCommand.js +60 -0
  28. package/main/manage/commands/FixerCommand.js +624 -0
  29. package/main/manage/commands/InitCommand.js +62 -0
  30. package/main/manage/commands/ScannerCommand.js +654 -0
  31. package/main/manage/commands/SizingCommand.js +60 -0
  32. package/main/manage/commands/SummaryCommand.js +61 -0
  33. package/main/manage/commands/UsageCommand.js +60 -0
  34. package/main/manage/commands/ValidateCommand.js +978 -0
  35. package/main/manage/index-fixed.js +1447 -0
  36. package/main/manage/index.js +1462 -0
  37. package/main/manage/managers/DebugMenu.js +140 -0
  38. package/main/manage/managers/InteractiveMenu.js +177 -0
  39. package/main/manage/managers/LanguageMenu.js +62 -0
  40. package/main/manage/managers/SettingsMenu.js +53 -0
  41. package/main/manage/services/AuthenticationService.js +263 -0
  42. package/main/manage/services/ConfigurationService-fixed.js +449 -0
  43. package/main/manage/services/ConfigurationService.js +449 -0
  44. package/main/manage/services/FileManagementService.js +368 -0
  45. package/main/manage/services/FrameworkDetectionService.js +458 -0
  46. package/main/manage/services/InitService.js +1051 -0
  47. package/main/manage/services/SetupService.js +462 -0
  48. package/main/manage/services/SummaryService.js +450 -0
  49. package/main/manage/services/UsageService.js +1502 -0
  50. package/package.json +32 -30
  51. package/runtime/enhanced.d.ts +221 -221
  52. package/runtime/index.d.ts +29 -29
  53. package/runtime/index.full.d.ts +331 -331
  54. package/runtime/index.js +7 -6
  55. package/scripts/build-lite.js +17 -17
  56. package/scripts/deprecate-versions.js +23 -6
  57. package/scripts/export-translations.js +5 -5
  58. package/scripts/fix-all-i18n.js +3 -3
  59. package/scripts/fix-and-purify-i18n.js +3 -2
  60. package/scripts/fix-locale-control-chars.js +110 -0
  61. package/scripts/lint-locales.js +80 -0
  62. package/scripts/locale-optimizer.js +8 -8
  63. package/scripts/prepublish.js +21 -21
  64. package/scripts/security-check.js +13 -5
  65. package/scripts/sync-translations.js +4 -4
  66. package/scripts/sync-ui-locales.js +9 -8
  67. package/scripts/validate-all-translations.js +8 -7
  68. package/scripts/verify-deprecations.js +23 -15
  69. package/scripts/verify-translations.js +6 -5
  70. package/settings/i18ntk-config.json +282 -282
  71. package/settings/language-config.json +5 -5
  72. package/settings/settings-cli.js +9 -9
  73. package/settings/settings-manager.js +23 -20
  74. package/ui-locales/de.json +2417 -2348
  75. package/ui-locales/en.json +2415 -2352
  76. package/ui-locales/es.json +2425 -2353
  77. package/ui-locales/fr.json +2418 -2348
  78. package/ui-locales/ja.json +2463 -2361
  79. package/ui-locales/ru.json +2463 -2359
  80. package/ui-locales/zh.json +2418 -2351
  81. package/utils/admin-auth.js +2 -2
  82. package/utils/admin-cli.js +297 -297
  83. package/utils/admin-pin.js +9 -9
  84. package/utils/cli-helper.js +9 -9
  85. package/utils/config-helper.js +152 -103
  86. package/utils/config-manager.js +204 -164
  87. package/utils/config.js +5 -4
  88. package/utils/env-manager.js +256 -0
  89. package/utils/framework-detector.js +27 -24
  90. package/utils/i18n-helper.js +85 -41
  91. package/utils/init-helper.js +152 -94
  92. package/utils/json-output.js +98 -98
  93. package/utils/logger.js +6 -2
  94. package/utils/mini-commander.js +179 -0
  95. package/utils/missing-key-validator.js +5 -5
  96. package/utils/plugin-loader.js +29 -11
  97. package/utils/prompt.js +14 -44
  98. package/utils/safe-json.js +40 -0
  99. package/utils/secure-errors.js +3 -3
  100. package/utils/security-check-improved.js +390 -0
  101. package/utils/security-config.js +5 -5
  102. package/utils/security-fixed.js +607 -0
  103. package/utils/security.js +462 -248
  104. package/utils/setup-enforcer.js +136 -44
  105. package/utils/setup-validator.js +33 -32
  106. package/utils/terminal-icons.js +1 -1
  107. package/utils/ultra-performance-optimizer.js +11 -9
  108. package/utils/watch-locales.js +2 -1
  109. package/utils/prompt-fixed.js +0 -55
  110. package/utils/security-check.js +0 -450
package/utils/security.js CHANGED
@@ -1,7 +1,23 @@
1
- const fs = require('fs');
2
1
  const path = require('path');
2
+ const fs = require('fs');
3
3
  const crypto = require('crypto');
4
- const configManager = require('./config-manager');
4
+
5
+
6
+ // Lazy load configManager to avoid circular dependency
7
+ let configManager;
8
+ let configManagerLoadAttempted = false;
9
+ function getConfigManager() {
10
+ if (!configManager && !configManagerLoadAttempted) {
11
+ configManagerLoadAttempted = true;
12
+ try {
13
+ configManager = require('./config-manager');
14
+ } catch (error) {
15
+ // Return null if config-manager can't be loaded
16
+ return null;
17
+ }
18
+ }
19
+ return configManager;
20
+ }
5
21
 
6
22
  // Lazy load i18n to prevent initialization race conditions
7
23
  let i18n;
@@ -24,17 +40,147 @@ function getI18n() {
24
40
  * to prevent path traversal, code injection, and other security vulnerabilities
25
41
  */
26
42
  class SecurityUtils {
43
+
44
+ // Static properties for operation tracking
45
+ static _operationStack = new Set();
46
+ static _logging = false;
47
+
48
+ constructor() {
49
+ // Instance constructor - static properties are already initialized
50
+ }
51
+
27
52
  /**
28
- * Validates and sanitizes file paths to prevent path traversal attacks
29
- * @param {string} inputPath - The input path to validate
30
- * @param {string} basePath - The base path that the input should be within (optional)
31
- * @returns {string|null} - Sanitized path or null if invalid
53
+ * Timeout wrapper for synchronous operations to prevent hanging
54
+ * @param {Function} operation - The synchronous operation to wrap
55
+ * @param {number} timeoutMs - Timeout in milliseconds
56
+ * @param {string} operationName - Name of the operation for logging
57
+ * @returns {*} - Operation result or null if timeout/error
32
58
  */
33
- static validatePath(filePath, basePath = process.cwd()) {
59
+ static withTimeoutSync(operation, timeoutMs = 5000, operationName = 'operation') {
60
+ // Track recursion to prevent infinite loops
61
+ if (!SecurityUtils._operationStack) {
62
+ SecurityUtils._operationStack = new Set();
63
+ }
64
+
65
+ if (SecurityUtils._operationStack.has(operationName)) {
66
+ const i18n = getI18n();
67
+ SecurityUtils.logSecurityEvent(i18n.t('security.recursion_detected', { operation: operationName }), 'error');
68
+ return null;
69
+ }
70
+
71
+ SecurityUtils._operationStack.add(operationName);
72
+
73
+ try {
74
+ // Simple timeout using setTimeout for synchronous operations
75
+ let result = null;
76
+ let hasResult = false;
77
+ let timeoutId = null;
78
+
79
+ timeoutId = setTimeout(() => {
80
+ if (!hasResult) {
81
+ const i18n = getI18n();
82
+ SecurityUtils.logSecurityEvent(i18n.t('security.operation_timeout', { operation: operationName }), 'warning');
83
+ }
84
+ }, timeoutMs);
85
+
86
+ // Execute operation synchronously
87
+ result = operation();
88
+ hasResult = true;
89
+
90
+ if (timeoutId) {
91
+ clearTimeout(timeoutId);
92
+ }
93
+
94
+ return result;
95
+ } catch (error) {
96
+ const i18n = getI18n();
97
+ console.warn(i18n.t('security.operation_error', { operation: operationName, error: error.message }));
98
+ return null;
99
+ } finally {
100
+ SecurityUtils._operationStack.delete(operationName);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Logs security events for monitoring
106
+ * @param {string} event - Security event description
107
+ * @param {string} level - Log level (info, warn, error)
108
+ * @param {object} details - Additional details
109
+ */
110
+ static logSecurityEvent(event, level = 'info', details = {}) {
111
+ // Prevent recursive logging which can occur during configuration loading
112
+ if (SecurityUtils._logging) {
113
+ return;
114
+ }
115
+
116
+ SecurityUtils._logging = true;
117
+ try {
118
+ const cfg = getConfigManager()?.getConfig?.() || {};
119
+ const envLevel = (process.env.SECURITY_LOG_LEVEL || process.env.I18NTK_SECURITY_LOG_LEVEL || '').toLowerCase();
120
+ const configLevel = (cfg.security?.logLevel || cfg.security?.audit?.logLevel || '').toLowerCase();
121
+ const currentLevel = envLevel || configLevel || 'warn';
122
+
123
+ const levels = { error: 0, warn: 1, warning: 1, info: 2 };
124
+ const messageLevel = levels[level.toLowerCase()] ?? 2;
125
+ const allowedLevel = levels[currentLevel] ?? 1;
126
+ if (messageLevel > allowedLevel) {
127
+ return;
128
+ }
129
+
130
+ const timestamp = new Date().toISOString();
131
+ const logEntry = {
132
+ timestamp,
133
+ level,
134
+ event,
135
+ details: {
136
+ ...details,
137
+ pid: process.pid,
138
+ nodeVersion: process.version
139
+ }
140
+ };
141
+
142
+ const message = `[SECURITY ${level.toUpperCase()}] ${timestamp}: ${event}`;
143
+ if (level === 'error') {
144
+ console.error(message, details);
145
+ } else if (level === 'warn' || level === 'warning') {
146
+ console.warn(message, details);
147
+ } else {
148
+ console.log(message, details);
149
+ }
150
+ } finally {
151
+ SecurityUtils._logging = false;
152
+ }
153
+ }
154
+
155
+ // Add other static methods here...
156
+ static validatePath(filePath, basePath = process.cwd(), verbose = false) {
157
+ const i18n = getI18n();
158
+ const useI18n = i18n && i18n.isInitialized && typeof i18n.t === 'function';
159
+
34
160
  try {
35
161
  if (!filePath || typeof filePath !== 'string') {
36
- const i18n = getI18n();
37
- SecurityUtils.logSecurityEvent(i18n.t('security.pathValidationFailed'), 'error', { inputPath: filePath, reason: i18n.t('security.invalidInputType') });
162
+ const message = useI18n
163
+ ? i18n.t('security.pathValidationFailed')
164
+ : 'Path validation failed';
165
+ const reason = useI18n
166
+ ? i18n.t('security.invalidInputType')
167
+ : 'Invalid input type';
168
+ SecurityUtils.logSecurityEvent(message, 'error', {
169
+ inputPath: filePath,
170
+ reason
171
+ });
172
+ return null;
173
+ }
174
+
175
+ // Check for obvious dangerous patterns first
176
+ if (!SecurityUtils.isSafePath(filePath)) {
177
+ const message = useI18n
178
+ ? i18n.t('security.pathTraversalAttempt')
179
+ : 'Path traversal attempt';
180
+ SecurityUtils.logSecurityEvent(message, 'warning', {
181
+ inputPath: filePath,
182
+ reason: 'Contains dangerous patterns'
183
+ });
38
184
  return null;
39
185
  }
40
186
 
@@ -50,65 +196,60 @@ class SecurityUtils {
50
196
  // If the path doesn't exist yet, fall back to the resolved path
51
197
  }
52
198
 
53
- // Ensure the target path is within the base directory
199
+ // Check for actual path traversal (going outside the base directory)
54
200
  const relativePath = path.relative(base, finalPath);
55
- if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
56
- const i18n = getI18n();
57
- SecurityUtils.logSecurityEvent(i18n.t('security.pathTraversalAttempt'), 'warning', { inputPath: filePath, resolvedPath: finalPath, basePath: base });
201
+ if (relativePath.startsWith('..')) {
202
+ const message = useI18n
203
+ ? i18n.t('security.pathTraversalAttempt')
204
+ : 'Path traversal attempt';
205
+ SecurityUtils.logSecurityEvent(message, 'warning', {
206
+ inputPath: filePath,
207
+ resolvedPath: finalPath,
208
+ basePath: base,
209
+ relativePath: relativePath
210
+ });
58
211
  return null;
59
212
  }
60
-
61
- const i18n = getI18n();
62
- SecurityUtils.logSecurityEvent(i18n.t('security.pathValidated'), 'info', { inputPath: filePath, resolvedPath: finalPath });
63
-
213
+
214
+ // Allow absolute paths that resolve within the project structure
215
+ // The isSafePath check above already filtered out dangerous absolute paths
216
+
217
+ if (verbose) {
218
+ const successMsg = useI18n
219
+ ? i18n.t('security.pathValidated')
220
+ : 'Path validated';
221
+ SecurityUtils.logSecurityEvent(successMsg, 'info', {
222
+ inputPath: filePath,
223
+ resolvedPath: finalPath
224
+ });
225
+ }
64
226
  return finalPath;
65
227
  } catch (error) {
66
- const i18n = getI18n();
67
- SecurityUtils.logSecurityEvent(i18n.t('security.pathValidationError'), 'error', { inputPath: filePath, error: error.message });
228
+ const message = useI18n
229
+ ? i18n.t('security.pathValidationError')
230
+ : 'Path validation error';
231
+ SecurityUtils.logSecurityEvent(message, 'error', {
232
+ inputPath: filePath,
233
+ error: error.message
234
+ });
68
235
  return null;
69
236
  }
70
237
  }
71
238
 
72
- /**
73
- * Safely reads a file with path validation and error handling
74
- * @param {string} filePath - Path to the file
75
- * @param {string} basePath - Base path for validation
76
- * @param {string} encoding - File encoding (default: 'utf8')
77
- * @returns {Promise<string|null>} - File content or null if error
78
- */
79
- static async safeReadFile(filePath, basePath, encoding = 'utf8') {
80
- const validatedPath = this.validatePath(filePath, basePath);
81
- if (!validatedPath) {
82
- return null;
83
- }
84
-
85
- try {
86
- // Check if file exists and is readable
87
- await fs.promises.access(validatedPath, fs.constants.R_OK);
88
-
89
- // Read file with size limit (10MB max)
90
- const stats = await fs.promises.stat(validatedPath);
91
- if (stats.size > 10 * 1024 * 1024) {
92
- const i18n = getI18n();
93
- console.warn(i18n.t('security.file_too_large', { filePath: validatedPath }));
94
- return null;
239
+ static safeExistsSync(filePath, basePath, timeoutMs = 3000) {
240
+ return this.withTimeoutSync(() => {
241
+ const validatedPath = this.validatePath(filePath, basePath);
242
+ if (!validatedPath) {
243
+ return false;
95
244
  }
96
-
97
- return await fs.promises.readFile(validatedPath, encoding);
98
- } catch (error) {
99
- const i18n = getI18n();
100
- console.warn(i18n.t('security.file_read_error', { errorMessage: error.message }));
101
- return null;
102
- }
245
+ try {
246
+ return fs.existsSync(validatedPath);
247
+ } catch (error) {
248
+ return false;
249
+ }
250
+ }, timeoutMs, 'safeExistsSync');
103
251
  }
104
252
 
105
- /**
106
- * Safely reads a file synchronously with path validation and error handling
107
- * @param {string} filePath - Path to the file
108
- * @param {string} basePath - Base path for validation
109
- * @param {string} encoding - File encoding (default: 'utf8')
110
- * @returns {string|null} - File content or null if error
111
- */
112
253
  static safeReadFileSync(filePath, basePath, encoding = 'utf8') {
113
254
  const validatedPath = this.validatePath(filePath, basePath);
114
255
  if (!validatedPath) {
@@ -118,14 +259,14 @@ class SecurityUtils {
118
259
  try {
119
260
  // Check if file exists and is readable
120
261
  fs.accessSync(validatedPath, fs.constants.R_OK);
121
-
262
+
122
263
  // Read file with size limit (10MB max)
123
264
  const stats = fs.statSync(validatedPath);
124
265
  if (stats.size > 10 * 1024 * 1024) {
125
266
  console.warn(i18n.t('security.file_too_large', { filePath: validatedPath }));
126
267
  return null;
127
268
  }
128
-
269
+
129
270
  return fs.readFileSync(validatedPath, encoding);
130
271
  } catch (error) {
131
272
  console.warn(i18n.t('security.file_read_error', { errorMessage: error.message }));
@@ -133,34 +274,34 @@ class SecurityUtils {
133
274
  }
134
275
  }
135
276
 
136
- /**
137
- * Safely writes a file with path validation and error handling
138
- * @param {string} filePath - Path to the file
139
- * @param {string} content - Content to write
140
- * @param {string} basePath - Base path for validation
141
- * @param {string} encoding - File encoding (default: 'utf8')
142
- * @returns {Promise<boolean>} - Success status
143
- */
144
- static async safeWriteFile(filePath, content, basePath, encoding = 'utf8') {
277
+ static safeWriteFileSync(filePath, content, basePath, encoding = 'utf8') {
145
278
  const validatedPath = this.validatePath(filePath, basePath);
146
279
  if (!validatedPath) {
147
280
  return false;
148
281
  }
149
282
 
150
283
  try {
284
+ // Validate content is a string or Buffer
285
+ if (typeof content !== 'string' && !Buffer.isBuffer(content)) {
286
+ const i18n = getI18n();
287
+ console.warn(i18n.t('security.file_write_error', { errorMessage: 'Content must be a string or Buffer' }));
288
+ return false;
289
+ }
290
+
151
291
  // Validate content size (10MB max)
152
- if (typeof content === 'string' && content.length > 10 * 1024 * 1024) {
292
+ const contentSize = typeof content === 'string' ? content.length : content.length;
293
+ if (contentSize > 10 * 1024 * 1024) {
153
294
  const i18n = getI18n();
154
295
  console.warn(i18n.t('security.content_too_large_for_file', { filePath: validatedPath }));
155
296
  return false;
156
297
  }
157
-
298
+
158
299
  // Ensure directory exists
159
300
  const dir = path.dirname(validatedPath);
160
- await fs.promises.mkdir(dir, { recursive: true });
161
-
301
+ fs.mkdirSync(dir, { recursive: true });
302
+
162
303
  // Write file with proper permissions
163
- await fs.promises.writeFile(validatedPath, content, { encoding, mode: 0o644 });
304
+ fs.writeFileSync(validatedPath, content, { encoding, mode: 0o644 });
164
305
  return true;
165
306
  } catch (error) {
166
307
  const i18n = getI18n();
@@ -170,37 +311,99 @@ class SecurityUtils {
170
311
  }
171
312
 
172
313
  /**
173
- * Safely parses JSON with error handling and validation
174
- * @param {string} jsonString - JSON string to parse
175
- * @param {number} maxSize - Maximum allowed size (default: 1MB)
176
- * @returns {object|null} - Parsed object or null if error
314
+ * Async compatibility wrapper for safeReadFileSync.
315
+ * @param {string} filePath
316
+ * @param {string} basePath
317
+ * @param {string} encoding
318
+ * @returns {Promise<string|null>}
177
319
  */
178
- static safeParseJSON(jsonString, maxSize = 1024 * 1024) {
179
- if (!jsonString || typeof jsonString !== 'string') {
320
+ static async safeReadFile(filePath, basePath, encoding = 'utf8') {
321
+ return this.safeReadFileSync(filePath, basePath, encoding);
322
+ }
323
+
324
+ /**
325
+ * Async compatibility wrapper for safeWriteFileSync.
326
+ * @param {string} filePath
327
+ * @param {string|Buffer} content
328
+ * @param {string} basePath
329
+ * @param {string} encoding
330
+ * @returns {Promise<boolean>}
331
+ */
332
+ static async safeWriteFile(filePath, content, basePath, encoding = 'utf8') {
333
+ return this.safeWriteFileSync(filePath, content, basePath, encoding);
334
+ }
335
+
336
+ static safeStatSync(filePath, basePath) {
337
+ const validatedPath = this.validatePath(filePath, basePath);
338
+ if (!validatedPath) {
180
339
  return null;
181
340
  }
182
-
183
- if (jsonString.length > maxSize) {
184
- const i18n = getI18n();
185
- console.warn(i18n.t('security.json_string_too_large'));
341
+ try {
342
+ return fs.statSync(validatedPath);
343
+ } catch {
186
344
  return null;
187
345
  }
346
+ }
188
347
 
348
+ static safeReaddirSync(dirPath, basePath, options) {
349
+ const validatedPath = this.validatePath(dirPath, basePath);
350
+ if (!validatedPath) {
351
+ return [];
352
+ }
189
353
  try {
190
- return JSON.parse(jsonString);
191
- } catch (error) {
192
- const i18n = getI18n();
193
- console.warn(i18n.t('security.json_parse_error', { errorMessage: error.message }));
194
- return null;
354
+ return fs.readdirSync(validatedPath, options);
355
+ } catch {
356
+ return [];
357
+ }
358
+ }
359
+
360
+ static safeMkdirSync(dirPath, basePath, options = { recursive: true }) {
361
+ const validatedPath = this.validatePath(dirPath, basePath);
362
+ if (!validatedPath) {
363
+ return false;
364
+ }
365
+ try {
366
+ fs.mkdirSync(validatedPath, options);
367
+ return true;
368
+ } catch {
369
+ return false;
195
370
  }
196
371
  }
197
372
 
198
373
  /**
199
- * Sanitizes user input to prevent injection attacks
200
- * @param {string} input - User input to sanitize
201
- * @param {object} options - Sanitization options
202
- * @returns {string} - Sanitized input
374
+ * Safely parse JSON content.
375
+ * Accepts both raw JSON strings and already-parsed objects.
376
+ * @param {string|object} input - JSON string or object
377
+ * @param {*} fallback - Value to return on parse error
378
+ * @returns {*}
203
379
  */
380
+ static safeParseJSON(input, fallback = null) {
381
+ if (input === null || input === undefined) {
382
+ return fallback;
383
+ }
384
+
385
+ if (typeof input === 'object') {
386
+ return input;
387
+ }
388
+
389
+ if (typeof input !== 'string') {
390
+ return fallback;
391
+ }
392
+
393
+ const trimmed = input.trim();
394
+ if (!trimmed) {
395
+ return fallback;
396
+ }
397
+
398
+ try {
399
+ const normalized = trimmed.charCodeAt(0) === 0xFEFF ? trimmed.slice(1) : trimmed;
400
+ return JSON.parse(normalized);
401
+ } catch (error) {
402
+ console.warn(`Invalid JSON content: ${error.message}`);
403
+ return fallback;
404
+ }
405
+ }
406
+
204
407
  static sanitizeInput(input, options = {}) {
205
408
  if (!input || typeof input !== 'string') {
206
409
  return '';
@@ -239,7 +442,7 @@ class SecurityUtils {
239
442
  const isFilePath = sanitized.includes('/') || sanitized.includes('\\') || sanitized.includes('.');
240
443
  const isCommonContent = sanitized.length < 1000 && !sanitized.includes('<script');
241
444
  if (!isFilePath && !isCommonContent) {
242
- const i18n = getI18n();
445
+ const i18n = getI18n();
243
446
  console.warn(i18n.t('security.inputDisallowedCharacters'));
244
447
  }
245
448
  // Allow more characters for file paths and content
@@ -249,143 +452,37 @@ class SecurityUtils {
249
452
  return sanitized;
250
453
  }
251
454
 
252
- /**
253
- * Validates command line arguments
254
- * @param {object} args - Command line arguments
255
- * @returns {object} - Validated arguments
256
- */
257
- static async validateCommandArgs(args) {
258
- const i18n = getI18n();
259
- const validatedArgs = {};
260
- const allowedArgs = [
261
- 'source-dir', 'i18n-dir', 'output-dir', 'output-report',
262
- 'help', 'language', 'strict-mode', 'exclude-files', 'no-prompt'
263
- ];
264
-
265
- for (const [key, value] of Object.entries(args)) {
266
- if (allowedArgs.includes(key)) {
267
- validatedArgs[key] = value;
268
- } else {
269
- console.warn(i18n.t('security.unknown_command_argument', { key }));
270
- }
271
- }
272
-
273
- return validatedArgs;
274
- }
275
-
276
- /**
277
- * Validates configuration object
278
- * @param {object} config - Configuration object
279
- * @returns {object|null} - Validated configuration or null if invalid
280
- */
281
- static validateConfig(config) {
282
- const i18n = getI18n();
283
- if (!config || typeof config !== 'object') {
284
- return null;
285
- }
286
-
287
- const validatedConfig = {};
288
- const allowedKeys = [
289
- 'version', 'sourceDir', 'outputDir', 'defaultLanguage', 'supportedLanguages',
290
- 'filePattern', 'excludePatterns', 'reportFormat', 'logLevel',
291
- 'i18nDir', 'sourceLanguage', 'excludeDirs', 'includeExtensions',
292
- 'translationPatterns', 'notTranslatedMarker', 'excludeFiles', 'strictMode',
293
- 'uiLanguage', 'language', 'sizeLimit', 'defaultLanguages', 'reportLanguage',
294
- 'theme', 'autoSave', 'notifications', 'dateFormat', 'timeFormat', 'timezone',
295
- 'processing', 'performance', 'advanced', 'security', 'debug', 'projectRoot', 'scriptDirectories',
296
- 'supportedExtensions', 'settings', 'backupDir', 'tempDir', 'cacheDir', 'configDir',
297
- 'displayPaths', 'reports', 'ui', 'behavior', 'dateTime', 'backup', 'framework',
298
- 'notTranslatedMarkers', 'placeholderStyles'
299
- ];
300
-
301
- const strict = config.security?.strictConfig || false;
302
-
303
- for (const [key, value] of Object.entries(config)) {
304
- if (!allowedKeys.includes(key)) {
305
- if (strict) {
306
- console.warn(i18n.t('security.unknown_config_key', { key }));
307
- }
308
- continue;
309
- }
310
-
311
- // Validate specific config values
312
- switch (key) {
313
- case 'sourceDir':
314
- case 'outputDir':
315
- if (typeof value === 'string') {
316
- // Basic path validation - will be further validated when used
317
- validatedConfig[key] = this.sanitizeInput(value, {
318
- allowedChars: /^[a-zA-Z0-9\-_\.\,\/\\\:\s]+$/,
319
- maxLength: 500
320
- });
321
- }
322
- break;
323
- case 'supportedLanguages':
324
- if (Array.isArray(value)) {
325
- validatedConfig[key] = value.filter(lang =>
326
- typeof lang === 'string' && /^[a-z]{2}(-[A-Z]{2})?$/.test(lang)
327
- );
328
- }
329
- break;
330
- case 'defaultLanguage':
331
- if (typeof value === 'string' && /^[a-z]{2}(-[A-Z]{2})?$/.test(value)) {
332
- validatedConfig[key] = value;
333
- }
334
- break;
335
- default:
336
- if (typeof value === 'string') {
337
- validatedConfig[key] = this.sanitizeInput(value);
338
- } else if (typeof value === 'boolean' || typeof value === 'number') {
339
- validatedConfig[key] = value;
340
- }
341
- }
342
- }
343
-
344
- return validatedConfig;
345
- }
346
-
347
- /**
348
- * Generates a secure hash for file integrity checking
349
- * @param {string} content - Content to hash
350
- * @returns {string} - SHA-256 hash
351
- */
352
455
  static generateHash(content) {
353
456
  return crypto.createHash('sha256').update(content).digest('hex');
354
457
  }
355
458
 
356
- /**
357
- * Securely saves an encrypted PIN to the settings directory
358
- * @param {string} pin - 4 digit PIN
359
- * @returns {Promise<boolean>} - success status
360
- */
361
- static async saveEncryptedPin(pin) {
362
- try {
363
- const hash = crypto.createHash('sha256').update(pin).digest('hex');
364
- const settingsDir = require('../settings/settings-manager').configDir;
365
- const pinFile = path.join(settingsDir, 'admin-pin.hash');
366
- await fs.promises.mkdir(settingsDir, { recursive: true });
367
- await fs.promises.writeFile(pinFile, hash, 'utf8');
368
- return true;
369
- } catch (error) {
370
- return false;
371
- }
372
- }
373
-
374
- /**
375
- * Checks if a file path is safe for operations
376
- * @param {string} filePath - File path to check
377
- * @returns {boolean} - Whether the path is safe
378
- */
379
459
  static isSafePath(filePath) {
380
460
  if (!filePath || typeof filePath !== 'string') {
381
461
  return false;
382
462
  }
383
463
 
384
- // Check for dangerous patterns
464
+ // Allow legitimate Windows drive letter paths
465
+ if (filePath.match(/^[A-Z]:[\/\\]/)) {
466
+ const afterDrive = filePath.substring(3);
467
+ // Only check the part after the drive letter for dangerous patterns
468
+ const dangerousPatterns = [
469
+ /\.\./, // Parent directory traversal
470
+ /~/, // Home directory
471
+ /\$\{/, // Variable expansion
472
+ /`/, // Command substitution
473
+ /\|/, // Pipe
474
+ /;/, // Command separator
475
+ /&/, // Background process
476
+ />/, // Redirect
477
+ /</ // Redirect
478
+ ];
479
+ return !dangerousPatterns.some(pattern => pattern.test(afterDrive));
480
+ }
481
+
482
+ // Check for dangerous patterns in non-Windows paths
385
483
  const dangerousPatterns = [
386
484
  /\.\./, // Parent directory traversal
387
- /^\//, // Absolute path (Unix)
388
- /^[A-Z]:\\/, // Absolute path (Windows)
485
+ /^\//, // Absolute path (Unix) - but allow for legitimate use
389
486
  /~/, // Home directory
390
487
  /\$\{/, // Variable expansion
391
488
  /`/, // Command substitution
@@ -396,43 +493,160 @@ class SecurityUtils {
396
493
  /</ // Redirect
397
494
  ];
398
495
 
496
+ // Allow absolute paths that are within the project structure
497
+ if (filePath.startsWith('/') || filePath.startsWith('\\')) {
498
+ // Allow absolute paths but check for dangerous patterns
499
+ const hasDangerousPatterns = dangerousPatterns.slice(1).some(pattern => pattern.test(filePath));
500
+ return !hasDangerousPatterns;
501
+ }
502
+
399
503
  return !dangerousPatterns.some(pattern => pattern.test(filePath));
400
504
  }
505
+ static validateConfig(config) {
506
+ if (!config || typeof config !== 'object') {
507
+ SecurityUtils.logSecurityEvent('Invalid configuration object provided', 'error', {
508
+ configType: typeof config
509
+ });
510
+ return {};
511
+ }
401
512
 
402
- /**
403
- * Logs security events for monitoring
404
- * @param {string} event - Security event description
405
- * @param {string} level - Log level (info, warn, error)
406
- * @param {object} details - Additional details
407
- */
408
- static logSecurityEvent(event, level = 'info', details = {}) {
409
- const timestamp = new Date().toISOString();
410
- const logEntry = {
411
- timestamp,
412
- level,
413
- event,
414
- details: {
415
- ...details,
416
- pid: process.pid,
417
- nodeVersion: process.version
418
- }
419
- };
513
+ const sanitized = { ...config };
514
+ const i18n = getI18n();
420
515
 
421
- // Only show security logs if debug mode is enabled and showSecurityLogs is true
422
- try {
423
- const cfg = configManager.getConfig();
424
- if (cfg.debug?.enabled && cfg.debug?.showSecurityLogs) {
425
- console.log(`[SECURITY ${level.toUpperCase()}] ${timestamp}: ${event}`, details);
516
+ // Define allowed configuration properties
517
+ const allowedProperties = new Set([
518
+ // Core directories and paths
519
+ 'projectRoot', 'sourceDir', 'i18nDir', 'outputDir', 'backupDir', 'tempDir', 'cacheDir', 'configDir',
520
+ // Language settings
521
+ 'sourceLanguage', 'uiLanguage', 'language', 'defaultLanguages', 'supportedLanguages',
522
+ // Translation markers and content
523
+ 'notTranslatedMarker', 'notTranslatedMarkers', 'translatedMarker', 'translatedMarkers',
524
+ // File handling
525
+ 'supportedExtensions', 'excludeFiles', 'excludeDirs', 'includeFiles', 'includeDirs',
526
+ // Operational settings
527
+ 'strictMode', 'debug', 'displayPaths', 'version', 'scriptDirectories',
528
+ // Framework and processing
529
+ 'framework', 'processing', 'performance', 'advanced',
530
+ // UI and theme settings
531
+ 'theme', 'ui', 'setup', 'reports', 'display', 'interface',
532
+ // Security and settings
533
+ 'security', 'settings', 'preferences', 'config', 'configuration',
534
+ // Additional common properties
535
+ 'autoSave', 'autoBackup', 'validateOnSave', 'showWarnings', 'verbose',
536
+ 'timeout', 'retries', 'batchSize', 'maxConcurrency', 'cacheEnabled'
537
+ ]);
538
+
539
+ // Remove unknown properties
540
+ Object.keys(sanitized).forEach(key => {
541
+ if (!allowedProperties.has(key)) {
542
+ // Only log warnings for properties that might be security risks
543
+ const value = sanitized[key];
544
+ const isSuspicious = typeof value === 'string' &&
545
+ (value.includes('..') || value.includes('/') || value.includes('\\') ||
546
+ value.includes('$') || value.includes('`') || value.includes('|') ||
547
+ value.includes(';') || value.includes('&'));
548
+
549
+ if (isSuspicious) {
550
+ SecurityUtils.logSecurityEvent('Removing potentially suspicious configuration property', 'warn', {
551
+ property: key,
552
+ value: sanitized[key]
553
+ });
554
+ } else {
555
+ // Use info level for normal unknown properties to reduce noise
556
+ SecurityUtils.logSecurityEvent('Removing unknown configuration property', 'info', {
557
+ property: key,
558
+ value: sanitized[key]
559
+ });
560
+ }
561
+ delete sanitized[key];
426
562
  }
427
- } catch (error) {
428
- // Fallback: if settings can't be loaded, don't show security logs to maintain clean UI
429
- // Only log critical security events in this case
430
- if (event.includes('CRITICAL') || event.includes('BREACH') || event.includes('ATTACK')) {
431
- const i18n = getI18n();
432
- console.log(i18n.t('security.security_alert', { timestamp, event }), details);
563
+ });
564
+
565
+ // Validate and sanitize path properties
566
+ const pathProperties = ['projectRoot', 'sourceDir', 'i18nDir', 'outputDir', 'backupDir', 'tempDir', 'cacheDir', 'configDir'];
567
+
568
+ pathProperties.forEach(prop => {
569
+ if (sanitized[prop] && typeof sanitized[prop] === 'string') {
570
+ let originalPath = sanitized[prop];
571
+
572
+ // Skip validation for legitimate absolute paths
573
+ const isWindowsAbsolute = originalPath.match(/^[A-Z]:[\/\\]/i);
574
+ const isUnixAbsolute = originalPath.startsWith('/') || originalPath.startsWith('\\');
575
+
576
+ if (isWindowsAbsolute || isUnixAbsolute) {
577
+ // Allow absolute paths - they're legitimate for project directories
578
+ return;
579
+ }
580
+
581
+ // Only validate relative paths for dangerous patterns
582
+ if (!SecurityUtils.isSafePath(originalPath)) {
583
+ SecurityUtils.logSecurityEvent('Path validation failed for configuration property', 'warn', {
584
+ property: prop,
585
+ originalPath: originalPath
586
+ });
587
+
588
+ // For relative paths, ensure they're safe
589
+ let sanitizedPath = originalPath.replace(/\.\.[\/\\]/g, '');
590
+ sanitizedPath = sanitizedPath.replace(/[|;&$`{}()[\]<>?]/g, '');
591
+
592
+ if (sanitizedPath !== originalPath) {
593
+ SecurityUtils.logSecurityEvent('Path sanitized for configuration property', 'info', {
594
+ property: prop,
595
+ originalPath: originalPath,
596
+ sanitizedPath: sanitizedPath
597
+ });
598
+ sanitized[prop] = sanitizedPath;
599
+ }
600
+ }
433
601
  }
602
+ });
603
+
604
+ // Validate security settings
605
+ if (sanitized.security) {
606
+ const security = sanitized.security;
607
+
608
+ // Validate session timeout (should be reasonable)
609
+ if (security.sessionTimeout && (typeof security.sessionTimeout !== 'number' || security.sessionTimeout < 60000 || security.sessionTimeout > 86400000)) {
610
+ SecurityUtils.logSecurityEvent('Invalid session timeout in security configuration', 'warn', {
611
+ sessionTimeout: security.sessionTimeout
612
+ });
613
+ security.sessionTimeout = 1800000; // Default to 30 minutes
614
+ }
615
+
616
+ // Validate max failed attempts
617
+ if (security.maxFailedAttempts && (typeof security.maxFailedAttempts !== 'number' || security.maxFailedAttempts < 1 || security.maxFailedAttempts > 10)) {
618
+ SecurityUtils.logSecurityEvent('Invalid max failed attempts in security configuration', 'warn', {
619
+ maxFailedAttempts: security.maxFailedAttempts
620
+ });
621
+ security.maxFailedAttempts = 3; // Default to 3 attempts
622
+ }
623
+ }
624
+
625
+ // Validate language settings
626
+ if (sanitized.sourceLanguage && typeof sanitized.sourceLanguage === 'string') {
627
+ // Sanitize language code (only allow alphanumeric, hyphens, underscores)
628
+ sanitized.sourceLanguage = sanitized.sourceLanguage.replace(/[^a-zA-Z0-9\-_]/g, '');
434
629
  }
630
+
631
+ if (sanitized.uiLanguage && typeof sanitized.uiLanguage === 'string') {
632
+ sanitized.uiLanguage = sanitized.uiLanguage.replace(/[^a-zA-Z0-9\-_]/g, '');
633
+ }
634
+
635
+ // Validate default languages array
636
+ if (sanitized.defaultLanguages && Array.isArray(sanitized.defaultLanguages)) {
637
+ sanitized.defaultLanguages = sanitized.defaultLanguages
638
+ .filter(lang => typeof lang === 'string')
639
+ .map(lang => lang.replace(/[^a-zA-Z0-9\-_]/g, ''))
640
+ .filter(lang => lang.length > 0);
641
+ }
642
+
643
+ SecurityUtils.logSecurityEvent('Configuration validation completed', 'info', {
644
+ propertiesCount: Object.keys(sanitized).length,
645
+ sanitizedPaths: pathProperties.filter(prop => sanitized[prop]).length
646
+ });
647
+
648
+ return sanitized;
435
649
  }
436
650
  }
437
651
 
438
- module.exports = SecurityUtils;
652
+ module.exports = SecurityUtils;