i18ntk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +401 -0
- package/LICENSE +21 -0
- package/README.md +507 -0
- package/dev/README.md +37 -0
- package/dev/debug/README.md +30 -0
- package/dev/debug/complete-console-translations.js +295 -0
- package/dev/debug/console-key-checker.js +408 -0
- package/dev/debug/console-translations.js +335 -0
- package/dev/debug/debugger.js +408 -0
- package/dev/debug/export-missing-keys.js +432 -0
- package/dev/debug/final-normalize.js +236 -0
- package/dev/debug/find-extra-keys.js +68 -0
- package/dev/debug/normalize-locales.js +153 -0
- package/dev/debug/refactor-locales.js +240 -0
- package/dev/debug/reorder-locales.js +85 -0
- package/dev/debug/replace-hardcoded-console.js +378 -0
- package/docs/INSTALLATION.md +449 -0
- package/docs/README.md +222 -0
- package/docs/TODO_ROADMAP.md +279 -0
- package/docs/api/API_REFERENCE.md +377 -0
- package/docs/api/COMPONENTS.md +492 -0
- package/docs/api/CONFIGURATION.md +651 -0
- package/docs/api/NPM_PUBLISHING_GUIDE.md +434 -0
- package/docs/debug/DEBUG_README.md +30 -0
- package/docs/debug/DEBUG_TOOLS.md +494 -0
- package/docs/development/AGENTS.md +351 -0
- package/docs/development/DEVELOPMENT_RULES.md +165 -0
- package/docs/development/DEV_README.md +37 -0
- package/docs/release-notes/RELEASE_NOTES_v1.0.0.md +173 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.0.md +141 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.1.md +185 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.3.md +199 -0
- package/docs/reports/ANALYSIS_README.md +17 -0
- package/docs/reports/CONSOLE_MISMATCH_BUG_REPORT_v1.5.0.md +181 -0
- package/docs/reports/SIZING_README.md +18 -0
- package/docs/reports/SUMMARY_README.md +18 -0
- package/docs/reports/TRANSLATION_BUG_REPORT_v1.5.0.md +129 -0
- package/docs/reports/USAGE_README.md +18 -0
- package/docs/reports/VALIDATION_README.md +18 -0
- package/locales/de/auth.json +3 -0
- package/locales/de/common.json +16 -0
- package/locales/de/pagination.json +6 -0
- package/locales/en/auth.json +3 -0
- package/locales/en/common.json +16 -0
- package/locales/en/pagination.json +6 -0
- package/locales/es/auth.json +3 -0
- package/locales/es/common.json +16 -0
- package/locales/es/pagination.json +6 -0
- package/locales/fr/auth.json +3 -0
- package/locales/fr/common.json +16 -0
- package/locales/fr/pagination.json +6 -0
- package/locales/ru/auth.json +3 -0
- package/locales/ru/common.json +16 -0
- package/locales/ru/pagination.json +6 -0
- package/main/i18ntk-analyze.js +625 -0
- package/main/i18ntk-autorun.js +461 -0
- package/main/i18ntk-complete.js +494 -0
- package/main/i18ntk-init.js +686 -0
- package/main/i18ntk-manage.js +848 -0
- package/main/i18ntk-sizing.js +557 -0
- package/main/i18ntk-summary.js +671 -0
- package/main/i18ntk-usage.js +1282 -0
- package/main/i18ntk-validate.js +762 -0
- package/main/ui-i18n.js +332 -0
- package/package.json +152 -0
- package/scripts/fix-missing-translation-keys.js +214 -0
- package/scripts/verify-package.js +168 -0
- package/ui-locales/de.json +637 -0
- package/ui-locales/en.json +688 -0
- package/ui-locales/es.json +637 -0
- package/ui-locales/fr.json +637 -0
- package/ui-locales/ja.json +637 -0
- package/ui-locales/ru.json +637 -0
- package/ui-locales/zh.json +637 -0
- package/utils/admin-auth.js +317 -0
- package/utils/admin-cli.js +353 -0
- package/utils/admin-pin.js +409 -0
- package/utils/detect-language-mismatches.js +454 -0
- package/utils/i18n-helper.js +128 -0
- package/utils/maintain-language-purity.js +433 -0
- package/utils/native-translations.js +478 -0
- package/utils/security.js +384 -0
- package/utils/test-complete-system.js +356 -0
- package/utils/test-console-i18n.js +402 -0
- package/utils/translate-mismatches.js +571 -0
- package/utils/validate-language-purity.js +531 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const SettingsManager = require('../settings/settings-manager');
|
|
5
|
+
const { t } = require('./i18n-helper');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Security utility module for i18nTK
|
|
9
|
+
* Provides secure file operations, path validation, and input sanitization
|
|
10
|
+
* to prevent path traversal, code injection, and other security vulnerabilities
|
|
11
|
+
*/
|
|
12
|
+
class SecurityUtils {
|
|
13
|
+
/**
|
|
14
|
+
* Validates and sanitizes file paths to prevent path traversal attacks
|
|
15
|
+
* @param {string} inputPath - The input path to validate
|
|
16
|
+
* @param {string} basePath - The base path that the input should be within (optional)
|
|
17
|
+
* @returns {string|null} - Sanitized path or null if invalid
|
|
18
|
+
*/
|
|
19
|
+
static validatePath(filePath, basePath = process.cwd()) {
|
|
20
|
+
try {
|
|
21
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
22
|
+
SecurityUtils.logSecurityEvent('path_validation_failed', {
|
|
23
|
+
inputPath: filePath,
|
|
24
|
+
reason: 'Invalid input type'
|
|
25
|
+
});
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Sanitize the input path
|
|
30
|
+
const sanitizedPath = path.normalize(filePath).replace(/\.\./g, '');
|
|
31
|
+
|
|
32
|
+
// Resolve the full path
|
|
33
|
+
const resolvedPath = path.resolve(basePath, sanitizedPath);
|
|
34
|
+
|
|
35
|
+
// Check if the resolved path is within the base path
|
|
36
|
+
const relativePath = path.relative(basePath, resolvedPath);
|
|
37
|
+
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
38
|
+
SecurityUtils.logSecurityEvent('path_traversal_attempt', {
|
|
39
|
+
inputPath: filePath,
|
|
40
|
+
resolvedPath,
|
|
41
|
+
basePath
|
|
42
|
+
});
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
SecurityUtils.logSecurityEvent('path_validated', {
|
|
47
|
+
inputPath: filePath,
|
|
48
|
+
resolvedPath
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return resolvedPath;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
SecurityUtils.logSecurityEvent('path_validation_error', {
|
|
54
|
+
inputPath: filePath,
|
|
55
|
+
error: error.message
|
|
56
|
+
});
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Safely reads a file with path validation and error handling
|
|
63
|
+
* @param {string} filePath - Path to the file
|
|
64
|
+
* @param {string} basePath - Base path for validation
|
|
65
|
+
* @param {string} encoding - File encoding (default: 'utf8')
|
|
66
|
+
* @returns {Promise<string|null>} - File content or null if error
|
|
67
|
+
*/
|
|
68
|
+
static async safeReadFile(filePath, basePath, encoding = 'utf8') {
|
|
69
|
+
const validatedPath = this.validatePath(filePath, basePath);
|
|
70
|
+
if (!validatedPath) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// Check if file exists and is readable
|
|
76
|
+
await fs.promises.access(validatedPath, fs.constants.R_OK);
|
|
77
|
+
|
|
78
|
+
// Read file with size limit (10MB max)
|
|
79
|
+
const stats = await fs.promises.stat(validatedPath);
|
|
80
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
81
|
+
console.warn(`Security: File too large: ${validatedPath}`);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return await fs.promises.readFile(validatedPath, encoding);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.warn(`Security: File read error: ${error.message}`);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Safely reads a file synchronously with path validation and error handling
|
|
94
|
+
* @param {string} filePath - Path to the file
|
|
95
|
+
* @param {string} basePath - Base path for validation
|
|
96
|
+
* @param {string} encoding - File encoding (default: 'utf8')
|
|
97
|
+
* @returns {string|null} - File content or null if error
|
|
98
|
+
*/
|
|
99
|
+
static safeReadFileSync(filePath, basePath, encoding = 'utf8') {
|
|
100
|
+
const validatedPath = this.validatePath(filePath, basePath);
|
|
101
|
+
if (!validatedPath) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
// Check if file exists and is readable
|
|
107
|
+
fs.accessSync(validatedPath, fs.constants.R_OK);
|
|
108
|
+
|
|
109
|
+
// Read file with size limit (10MB max)
|
|
110
|
+
const stats = fs.statSync(validatedPath);
|
|
111
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
112
|
+
console.warn(`Security: File too large: ${validatedPath}`);
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return fs.readFileSync(validatedPath, encoding);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.warn(`Security: File read error: ${error.message}`);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Safely writes a file with path validation and error handling
|
|
125
|
+
* @param {string} filePath - Path to the file
|
|
126
|
+
* @param {string} content - Content to write
|
|
127
|
+
* @param {string} basePath - Base path for validation
|
|
128
|
+
* @param {string} encoding - File encoding (default: 'utf8')
|
|
129
|
+
* @returns {Promise<boolean>} - Success status
|
|
130
|
+
*/
|
|
131
|
+
static async safeWriteFile(filePath, content, basePath, encoding = 'utf8') {
|
|
132
|
+
const validatedPath = this.validatePath(filePath, basePath);
|
|
133
|
+
if (!validatedPath) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
// Validate content size (10MB max)
|
|
139
|
+
if (typeof content === 'string' && content.length > 10 * 1024 * 1024) {
|
|
140
|
+
console.warn(`Security: Content too large for file: ${validatedPath}`);
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Ensure directory exists
|
|
145
|
+
const dir = path.dirname(validatedPath);
|
|
146
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
147
|
+
|
|
148
|
+
// Write file with proper permissions
|
|
149
|
+
await fs.promises.writeFile(validatedPath, content, { encoding, mode: 0o644 });
|
|
150
|
+
return true;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.warn(`Security: File write error: ${error.message}`);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Safely parses JSON with error handling and validation
|
|
159
|
+
* @param {string} jsonString - JSON string to parse
|
|
160
|
+
* @param {number} maxSize - Maximum allowed size (default: 1MB)
|
|
161
|
+
* @returns {object|null} - Parsed object or null if error
|
|
162
|
+
*/
|
|
163
|
+
static safeParseJSON(jsonString, maxSize = 1024 * 1024) {
|
|
164
|
+
if (!jsonString || typeof jsonString !== 'string') {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (jsonString.length > maxSize) {
|
|
169
|
+
console.warn('Security: JSON string too large');
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
return JSON.parse(jsonString);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.warn(`Security: JSON parse error: ${error.message}`);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Sanitizes user input to prevent injection attacks
|
|
183
|
+
* @param {string} input - User input to sanitize
|
|
184
|
+
* @param {object} options - Sanitization options
|
|
185
|
+
* @returns {string} - Sanitized input
|
|
186
|
+
*/
|
|
187
|
+
static sanitizeInput(input, options = {}) {
|
|
188
|
+
if (!input || typeof input !== 'string') {
|
|
189
|
+
return '';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const {
|
|
193
|
+
allowedChars = /^[a-zA-Z0-9\s\-_\.\,\!\?\(\)\[\]\{\}\:;"']+$/,
|
|
194
|
+
maxLength = 1000,
|
|
195
|
+
removeHTML = true,
|
|
196
|
+
removeScripts = true
|
|
197
|
+
} = options;
|
|
198
|
+
|
|
199
|
+
let sanitized = input.trim();
|
|
200
|
+
|
|
201
|
+
// Limit length
|
|
202
|
+
if (sanitized.length > maxLength) {
|
|
203
|
+
sanitized = sanitized.substring(0, maxLength);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Remove HTML tags if requested
|
|
207
|
+
if (removeHTML) {
|
|
208
|
+
sanitized = sanitized.replace(/<[^>]*>/g, '');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Remove script-like content
|
|
212
|
+
if (removeScripts) {
|
|
213
|
+
sanitized = sanitized.replace(/javascript:/gi, '');
|
|
214
|
+
sanitized = sanitized.replace(/on\w+\s*=/gi, '');
|
|
215
|
+
sanitized = sanitized.replace(/eval\s*\(/gi, '');
|
|
216
|
+
sanitized = sanitized.replace(/function\s*\(/gi, '');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Check against allowed characters
|
|
220
|
+
if (!allowedChars.test(sanitized)) {
|
|
221
|
+
console.warn('Security: Input contains disallowed characters');
|
|
222
|
+
// Remove disallowed characters
|
|
223
|
+
sanitized = sanitized.replace(/[^a-zA-Z0-9\s\-_\.\,\!\?\(\)\[\]\{\}\:;"']/g, '');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return sanitized;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Validates command line arguments
|
|
231
|
+
* @param {object} args - Command line arguments
|
|
232
|
+
* @returns {object} - Validated arguments
|
|
233
|
+
*/
|
|
234
|
+
static async validateCommandArgs(args) {
|
|
235
|
+
const validatedArgs = {};
|
|
236
|
+
const allowedArgs = [
|
|
237
|
+
'source-dir', 'i18n-dir', 'output-dir', 'output-report',
|
|
238
|
+
'help', 'language', 'strict-mode', 'exclude-files'
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
for (const [key, value] of Object.entries(args)) {
|
|
242
|
+
if (allowedArgs.includes(key)) {
|
|
243
|
+
validatedArgs[key] = value;
|
|
244
|
+
} else {
|
|
245
|
+
console.warn(`${t('hardcodedTexts.securityUnknownCommandArg')}: ${key}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return validatedArgs;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Validates configuration object
|
|
254
|
+
* @param {object} config - Configuration object
|
|
255
|
+
* @returns {object|null} - Validated configuration or null if invalid
|
|
256
|
+
*/
|
|
257
|
+
static validateConfig(config) {
|
|
258
|
+
if (!config || typeof config !== 'object') {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const validatedConfig = {};
|
|
263
|
+
const allowedKeys = [
|
|
264
|
+
'sourceDir', 'outputDir', 'defaultLanguage', 'supportedLanguages',
|
|
265
|
+
'filePattern', 'excludePatterns', 'reportFormat', 'logLevel',
|
|
266
|
+
'i18nDir', 'sourceLanguage', 'excludeDirs', 'includeExtensions',
|
|
267
|
+
'translationPatterns', 'notTranslatedMarker', 'excludeFiles', 'strictMode'
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
for (const [key, value] of Object.entries(config)) {
|
|
271
|
+
if (!allowedKeys.includes(key)) {
|
|
272
|
+
console.warn(`${t('hardcodedTexts.securityUnknownConfigKey')}: ${key}`);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Validate specific config values
|
|
277
|
+
switch (key) {
|
|
278
|
+
case 'sourceDir':
|
|
279
|
+
case 'outputDir':
|
|
280
|
+
if (typeof value === 'string') {
|
|
281
|
+
// Basic path validation - will be further validated when used
|
|
282
|
+
validatedConfig[key] = this.sanitizeInput(value, {
|
|
283
|
+
allowedChars: /^[a-zA-Z0-9\-_\.\,\/\\\:]+$/,
|
|
284
|
+
maxLength: 500
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
case 'supportedLanguages':
|
|
289
|
+
if (Array.isArray(value)) {
|
|
290
|
+
validatedConfig[key] = value.filter(lang =>
|
|
291
|
+
typeof lang === 'string' && /^[a-z]{2}(-[A-Z]{2})?$/.test(lang)
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
break;
|
|
295
|
+
case 'defaultLanguage':
|
|
296
|
+
if (typeof value === 'string' && /^[a-z]{2}(-[A-Z]{2})?$/.test(value)) {
|
|
297
|
+
validatedConfig[key] = value;
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
default:
|
|
301
|
+
if (typeof value === 'string') {
|
|
302
|
+
validatedConfig[key] = this.sanitizeInput(value);
|
|
303
|
+
} else if (typeof value === 'boolean' || typeof value === 'number') {
|
|
304
|
+
validatedConfig[key] = value;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return validatedConfig;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Generates a secure hash for file integrity checking
|
|
314
|
+
* @param {string} content - Content to hash
|
|
315
|
+
* @returns {string} - SHA-256 hash
|
|
316
|
+
*/
|
|
317
|
+
static generateHash(content) {
|
|
318
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Checks if a file path is safe for operations
|
|
323
|
+
* @param {string} filePath - File path to check
|
|
324
|
+
* @returns {boolean} - Whether the path is safe
|
|
325
|
+
*/
|
|
326
|
+
static isSafePath(filePath) {
|
|
327
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Check for dangerous patterns
|
|
332
|
+
const dangerousPatterns = [
|
|
333
|
+
/\.\./, // Parent directory traversal
|
|
334
|
+
/^\//, // Absolute path (Unix)
|
|
335
|
+
/^[A-Z]:\\/, // Absolute path (Windows)
|
|
336
|
+
/~/, // Home directory
|
|
337
|
+
/\$\{/, // Variable expansion
|
|
338
|
+
/`/, // Command substitution
|
|
339
|
+
/\|/, // Pipe
|
|
340
|
+
/;/, // Command separator
|
|
341
|
+
/&/, // Background process
|
|
342
|
+
/>/, // Redirect
|
|
343
|
+
/</ // Redirect
|
|
344
|
+
];
|
|
345
|
+
|
|
346
|
+
return !dangerousPatterns.some(pattern => pattern.test(filePath));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Logs security events for monitoring
|
|
351
|
+
* @param {string} event - Security event description
|
|
352
|
+
* @param {string} level - Log level (info, warn, error)
|
|
353
|
+
* @param {object} details - Additional details
|
|
354
|
+
*/
|
|
355
|
+
static logSecurityEvent(event, level = 'info', details = {}) {
|
|
356
|
+
const timestamp = new Date().toISOString();
|
|
357
|
+
const logEntry = {
|
|
358
|
+
timestamp,
|
|
359
|
+
level,
|
|
360
|
+
event,
|
|
361
|
+
details: {
|
|
362
|
+
...details,
|
|
363
|
+
pid: process.pid,
|
|
364
|
+
nodeVersion: process.version
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// Only show security logs if debug mode is enabled and showSecurityLogs is true
|
|
369
|
+
try {
|
|
370
|
+
const settingsManager = SettingsManager;
|
|
371
|
+
if (settingsManager.shouldShowSecurityLogs()) {
|
|
372
|
+
console.log(`[SECURITY ${level.toUpperCase()}] ${timestamp}: ${event}`, details);
|
|
373
|
+
}
|
|
374
|
+
} catch (error) {
|
|
375
|
+
// Fallback: if settings can't be loaded, don't show security logs to maintain clean UI
|
|
376
|
+
// Only log critical security events in this case
|
|
377
|
+
if (event.includes('CRITICAL') || event.includes('BREACH') || event.includes('ATTACK')) {
|
|
378
|
+
console.log(`[SECURITY ALERT] ${timestamp}: ${event}`, details);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
module.exports = SecurityUtils;
|