i18ntk 2.3.8 → 2.5.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.
@@ -1,393 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * i18ntk Security Check Utility - IMPROVED VERSION
5
- * Performs comprehensive security validation before build/publish
6
- * Enhanced to intelligently distinguish between safe and dangerous requires
7
- */
8
-
9
- const fs = require('fs');
10
- const path = require('path');
11
- const crypto = require('crypto');
12
-
13
- class SecurityChecker {
14
- constructor() {
15
- this.issues = [];
16
- this.warnings = [];
17
- this.projectRoot = path.resolve(__dirname, '..');
18
- }
19
-
20
- log(message, type = 'info') {
21
- const timestamp = new Date().toISOString();
22
- const colors = {
23
- error: '\x1b[31m',
24
- warning: '\x1b[33m',
25
- success: '\x1b[32m',
26
- info: '\x1b[36m',
27
- reset: '\x1b[0m'
28
- };
29
- console.log(`${colors[type]}[${timestamp}] ${message}${colors.reset}`);
30
- }
31
-
32
- addIssue(message, file = null, line = null) {
33
- this.issues.push({ message, file, line, type: 'error' });
34
- }
35
-
36
- addWarning(message, file = null, line = null) {
37
- this.warnings.push({ message, file, line, type: 'warning' });
38
- }
39
-
40
- async checkFileExists(filePath) {
41
- try {
42
- await fs.promises.access(filePath);
43
- return true;
44
- } catch {
45
- return false;
46
- }
47
- }
48
-
49
- async readFile(filePath) {
50
- try {
51
- return await fs.promises.readFile(filePath, 'utf8');
52
- } catch (error) {
53
- this.addIssue(`Cannot read file: ${filePath}`, filePath);
54
- return null;
55
- }
56
- }
57
-
58
- async checkPackageJson() {
59
- this.log('Checking package.json security...');
60
-
61
- const packageJsonPath = path.join(this.projectRoot, 'package.json');
62
- const content = await this.readFile(packageJsonPath);
63
-
64
- if (!content) return;
65
-
66
- try {
67
- const pkg = JSON.parse(content);
68
-
69
- // Check for dangerous scripts
70
- const dangerousScripts = ['preinstall', 'postinstall', 'preuninstall', 'postuninstall'];
71
- const scripts = pkg.scripts || {};
72
-
73
- for (const script of dangerousScripts) {
74
- if (scripts[script]) {
75
- this.addWarning(`Potentially dangerous script found: ${script}`, packageJsonPath);
76
- }
77
- }
78
-
79
- // Check dependencies for known vulnerabilities (basic check)
80
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
81
- for (const [dep, version] of Object.entries(allDeps || {})) {
82
- if (version.includes('*') || version.includes('latest')) {
83
- this.addWarning(`Unpinned dependency version: ${dep}@${version}`, packageJsonPath);
84
- }
85
- }
86
-
87
- // Verify security scripts exist
88
- const requiredScripts = ['security:check', 'security:test', 'security:audit'];
89
- for (const script of requiredScripts) {
90
- if (!scripts[script]) {
91
- this.addIssue(`Missing required security script: ${script}`, packageJsonPath);
92
- }
93
- }
94
-
95
- } catch (error) {
96
- this.addIssue(`Invalid JSON in package.json: ${error.message}`, packageJsonPath);
97
- }
98
- }
99
-
100
- async checkSecurityUtils() {
101
- this.log('Checking SecurityUtils implementation...');
102
-
103
- const securityUtilsPath = path.join(this.projectRoot, 'utils/security.js');
104
- if (!(await this.checkFileExists(securityUtilsPath))) {
105
- this.addIssue('SecurityUtils file not found', securityUtilsPath);
106
- return;
107
- }
108
-
109
- const content = await this.readFile(securityUtilsPath);
110
- if (!content) return;
111
-
112
- // Check for required security methods
113
- const requiredMethods = [
114
- 'safeReadFileSync',
115
- 'safeExistsSync',
116
- 'safeWriteFileSync',
117
- 'validatePath',
118
- 'sanitizeInput',
119
- 'safeParseJSON'
120
- ];
121
-
122
- for (const method of requiredMethods) {
123
- if (!content.includes(method)) {
124
- this.addIssue(`Missing security method: ${method}`, securityUtilsPath);
125
- }
126
- }
127
-
128
- // Check for dangerous code execution patterns in SecurityUtils itself.
129
- // Direct fs usage is expected here because this module provides vetted wrappers.
130
- const dangerousPatterns = [
131
- /eval\s*\(/g,
132
- /Function\s*\(/g
133
- ];
134
-
135
- for (const pattern of dangerousPatterns) {
136
- const matches = content.match(pattern);
137
- if (matches) {
138
- this.addWarning(`Potentially unsafe pattern found: ${pattern}`, securityUtilsPath);
139
- }
140
- }
141
- }
142
-
143
- async checkSourceFiles() {
144
- this.log('Checking source files for security issues...');
145
-
146
- const sourceDirs = ['main', 'utils', 'scripts', 'settings'];
147
- const excludeFiles = ['security.js', 'security-fixed.js', 'security-check.js', 'security-check-improved.js'];
148
-
149
- for (const dir of sourceDirs) {
150
- const dirPath = path.join(this.projectRoot, dir);
151
- if (!(await this.checkFileExists(dirPath))) continue;
152
-
153
- try {
154
- const files = await fs.promises.readdir(dirPath);
155
- for (const file of files) {
156
- if (!file.endsWith('.js') || excludeFiles.includes(file)) continue;
157
-
158
- const filePath = path.join(dirPath, file);
159
- const content = await this.readFile(filePath);
160
- if (!content) continue;
161
-
162
- await this.analyzeFileSecurity(filePath, content);
163
- }
164
- } catch (error) {
165
- this.addIssue(`Cannot read directory: ${dirPath}`, dirPath);
166
- }
167
- }
168
- }
169
-
170
- async analyzeFileSecurity(filePath, content) {
171
- const lines = content.split('\n');
172
-
173
- lines.forEach((line, index) => {
174
- // Check for direct fs operations
175
- if (line.includes('fs.readFileSync(') && !line.includes('SecurityUtils')) {
176
- this.addIssue('Direct fs.readFileSync usage (use SecurityUtils.safeReadFileSync)', filePath, index + 1);
177
- }
178
- if (line.includes('fs.writeFileSync(') && !line.includes('SecurityUtils')) {
179
- this.addIssue('Direct fs.writeFileSync usage (use SecurityUtils.safeWriteFileSync)', filePath, index + 1);
180
- }
181
- if (line.includes('fs.existsSync(') && !line.includes('SecurityUtils')) {
182
- this.addIssue('Direct fs.existsSync usage (use SecurityUtils.safeExistsSync)', filePath, index + 1);
183
- }
184
-
185
- // Check for dangerous patterns
186
- if (line.includes('eval(') || line.includes('Function(')) {
187
- this.addIssue('Dangerous code execution pattern detected', filePath, index + 1);
188
- }
189
-
190
- // Check for unsafe require patterns - be more intelligent
191
- if (line.includes('require(')) {
192
- this.analyzeRequireStatement(line, filePath, index + 1);
193
- }
194
- });
195
- }
196
-
197
- analyzeRequireStatement(line, filePath, lineNumber) {
198
- // Extract the require path
199
- const requireMatch = line.match(/require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/);
200
- if (!requireMatch) return;
201
-
202
- const requirePath = requireMatch[1];
203
-
204
- // Skip safe built-in modules
205
- // Note: child_process is intentionally excluded to keep runtime zero-shell
206
- const safeBuiltins = ['fs', 'path', 'crypto', 'os', 'util', 'events', 'stream', 'buffer', 'http', 'https', 'url', 'querystring'];
207
- if (safeBuiltins.includes(requirePath)) {
208
- return; // Safe built-in module
209
- }
210
-
211
- // Skip safe relative requires within project structure
212
- if (requirePath.startsWith('../') || requirePath.startsWith('./')) {
213
- // Check if it's going too far up (more than 2 levels)
214
- const upLevels = (requirePath.match(/\.\.\//g) || []).length;
215
- if (upLevels > 2) {
216
- this.addWarning('Deep relative require (more than 2 levels up)', filePath, lineNumber);
217
- }
218
- // Otherwise, relative requires within project are generally safe
219
- return;
220
- }
221
-
222
- // Check for dynamic requires (variables)
223
- if (requirePath.includes('${') || requirePath.includes('+') || requirePath.includes('variable')) {
224
- this.addIssue('Dynamic require statement detected (potential security risk)', filePath, lineNumber);
225
- return;
226
- }
227
-
228
- // Check for absolute paths outside node_modules
229
- if (requirePath.startsWith('/') && !requirePath.includes('node_modules')) {
230
- this.addWarning('Absolute path require outside node_modules', filePath, lineNumber);
231
- return;
232
- }
233
-
234
- // Check for suspicious patterns
235
- const suspiciousPatterns = [
236
- /\.\./, // path traversal
237
- /^~/, // home directory shorthand
238
- /\$(HOME|USER)\b/, // shell env expansions
239
- /^[a-z][a-z0-9+.-]*:/i // URL/protocol-like require targets
240
- ];
241
- for (const pattern of suspiciousPatterns) {
242
- if (pattern.test(requirePath)) {
243
- this.addIssue(`Suspicious require path pattern: ${pattern}`, filePath, lineNumber);
244
- return;
245
- }
246
- }
247
-
248
- // If we get here, it's likely a safe npm package require
249
- // No action needed for legitimate package requires
250
- }
251
-
252
- async checkFilePermissions() {
253
- this.log('Checking file permissions...');
254
-
255
- // POSIX permission checks are noisy/non-actionable on Windows.
256
- if (process.platform === 'win32') {
257
- return;
258
- }
259
-
260
- const criticalFiles = [
261
- 'utils/security.js',
262
- 'tests/security.test.js',
263
- 'package.json'
264
- ];
265
-
266
- for (const file of criticalFiles) {
267
- const filePath = path.join(this.projectRoot, file);
268
- if (!(await this.checkFileExists(filePath))) {
269
- this.addIssue(`Critical file not found: ${file}`, filePath);
270
- continue;
271
- }
272
-
273
- try {
274
- const stats = await fs.promises.stat(filePath);
275
- const permissions = (stats.mode & parseInt('777', 8)).toString(8);
276
-
277
- // Check if file is writable by group or others
278
- if (permissions[1] !== '0' || permissions[2] !== '0') {
279
- this.addWarning(`File has overly permissive permissions: ${file} (${permissions})`, filePath);
280
- }
281
- } catch (error) {
282
- this.addIssue(`Cannot check permissions for: ${file}`, filePath);
283
- }
284
- }
285
- }
286
-
287
- async checkDependencies() {
288
- this.log('Checking for dependency vulnerabilities...');
289
-
290
- const packageJsonPath = path.join(this.projectRoot, 'package.json');
291
- const content = await this.readFile(packageJsonPath);
292
-
293
- if (!content) return;
294
-
295
- try {
296
- const pkg = JSON.parse(content);
297
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
298
-
299
- // Check for zero dependencies claim
300
- if (Object.keys(allDeps || {}).length > 0) {
301
- this.addWarning('Package claims zero dependencies but has dependencies in package.json');
302
- }
303
-
304
- // Check for suspicious dependency names
305
- const suspiciousDeps = ['malicious', 'hack', 'exploit', 'trojan'];
306
- for (const dep of Object.keys(allDeps || {})) {
307
- for (const suspicious of suspiciousDeps) {
308
- if (dep.toLowerCase().includes(suspicious)) {
309
- this.addIssue(`Suspicious dependency name: ${dep}`);
310
- }
311
- }
312
- }
313
- } catch (error) {
314
- this.addIssue(`Cannot parse package.json: ${error.message}`, packageJsonPath);
315
- }
316
- }
317
-
318
- async run() {
319
- this.log('Starting i18ntk Security Check (IMPROVED VERSION)...', 'info');
320
-
321
- try {
322
- await this.checkPackageJson();
323
- await this.checkSecurityUtils();
324
- await this.checkSourceFiles();
325
- await this.checkFilePermissions();
326
- await this.checkDependencies();
327
-
328
- // Generate report
329
- this.generateReport();
330
-
331
- // Final status with detailed counts
332
- const totalIssues = this.issues.length + this.warnings.length;
333
- if (this.issues.length > 0) {
334
- this.log(`Security check FAILED: ${this.issues.length} critical issues, ${this.warnings.length} warnings found`, 'error');
335
- this.log(`Total: ${totalIssues} issues detected`, 'error');
336
- // Ensure output is flushed before exit
337
- await new Promise(resolve => setImmediate(resolve));
338
- process.exit(1);
339
- } else if (this.warnings.length > 0) {
340
- this.log('Security check PASSED: No critical issues found', 'success');
341
- this.log(`${this.warnings.length} warnings found (non-blocking)`, 'warning');
342
- this.log(`Total: ${totalIssues} issues detected`, 'warning');
343
- } else {
344
- this.log('Security check PASSED: No issues found', 'success');
345
- }
346
- } catch (error) {
347
- this.log(`Security check failed with error: ${error.message}`, 'error');
348
- console.error('Stack trace:', error.stack);
349
- process.exit(1);
350
- }
351
- }
352
-
353
- generateReport() {
354
- if (this.issues.length === 0 && this.warnings.length === 0) {
355
- return;
356
- }
357
-
358
- console.log('\n=== SECURITY CHECK REPORT (IMPROVED) ===\n');
359
-
360
- if (this.issues.length > 0) {
361
- console.log('🔴 CRITICAL ISSUES:');
362
- this.issues.forEach(issue => {
363
- console.log(` • ${issue.message}`);
364
- if (issue.file) {
365
- console.log(` File: ${issue.file}${issue.line ? `:${issue.line}` : ''}`);
366
- }
367
- });
368
- console.log('');
369
- }
370
-
371
- if (this.warnings.length > 0) {
372
- console.log('🟡 WARNINGS:');
373
- this.warnings.forEach(warning => {
374
- console.log(` • ${warning.message}`);
375
- if (warning.file) {
376
- console.log(` File: ${warning.file}${warning.line ? `:${warning.line}` : ''}`);
377
- }
378
- });
379
- console.log('');
380
- }
381
- }
382
- }
383
-
384
- // Run security check if called directly
385
- if (require.main === module) {
386
- const checker = new SecurityChecker();
387
- checker.run().catch(error => {
388
- console.error('Security check failed:', error);
389
- process.exit(1);
390
- });
391
- }
392
-
393
- module.exports = SecurityChecker;
@@ -1,239 +0,0 @@
1
- /**
2
- * Security Configuration Utility
3
- * Provides secure configuration management for the i18n Management Toolkit
4
- */
5
-
6
- const crypto = require('crypto');
7
- const fs = require('fs');
8
- const path = require('path');
9
-
10
- class SecurityConfig {
11
- constructor() {
12
- this.configDir = path.resolve(__dirname, '..');
13
- this.configPath = path.join(this.configDir, 'security-config.json');
14
- this.securityDefaults = {
15
- pin: {
16
- minLength: 4,
17
- maxLength: 32,
18
- requireStrongPin: true,
19
- maxAttempts: 3,
20
- lockDuration: 300000, // 5 minutes
21
- algorithm: 'scrypt'
22
- },
23
- encryption: {
24
- algorithm: 'aes-256-gcm',
25
- keyLength: 32,
26
- ivLength: 12,
27
- authTagLength: 16
28
- },
29
- audit: {
30
- enabled: true,
31
- logLevel: 'info',
32
- retentionDays: 30
33
- }
34
- };
35
- }
36
-
37
- /**
38
- * Generate secure configuration with settings-based configuration
39
- */
40
- generateSecureConfig(settings = {}) {
41
- const config = {
42
- ...this.securityDefaults,
43
- secrets: {
44
- adminPin: settings.adminPin || null,
45
- encryptionKey: settings.encryptionKey || this.generateSecureKey(),
46
- jwtSecret: settings.jwtSecret || this.generateSecureKey()
47
- },
48
- security: {
49
- ...this.securityDefaults,
50
- environment: 'production',
51
- disableWeakPinWarning: settings.disableWeakPinWarning === true
52
- }
53
- };
54
-
55
- return config;
56
- }
57
-
58
- /**
59
- * Generate cryptographically secure random key
60
- */
61
- generateSecureKey(length = 32) {
62
- return crypto.randomBytes(length).toString('hex');
63
- }
64
-
65
- /**
66
- * Validate security configuration
67
- */
68
- validateSecurityConfig(config) {
69
- const errors = [];
70
-
71
- // PIN validation
72
- if (config.secrets?.adminPin) {
73
- if (config.secrets.adminPin.length < config.security.pin.minLength) {
74
- errors.push(`PIN must be at least ${config.security.pin.minLength} characters`);
75
- }
76
- if (this.isWeakPin(config.secrets.adminPin)) {
77
- errors.push('PIN appears to be weak - consider using a stronger PIN');
78
- }
79
- }
80
-
81
- // Encryption key validation
82
- if (config.secrets?.encryptionKey) {
83
- if (config.secrets.encryptionKey.length < 64) { // 32 bytes hex encoded
84
- errors.push('Encryption key must be at least 32 bytes (64 hex chars)');
85
- }
86
- }
87
-
88
- return {
89
- valid: errors.length === 0,
90
- errors
91
- };
92
- }
93
-
94
- /**
95
- * Check if PIN is weak
96
- */
97
- isWeakPin(pin) {
98
- const weakPatterns = [
99
- /^\d{1,4}$/, // Only 1-4 digits
100
- /^(.)\1+$/, // All same characters
101
- '1234', '0000', '1111', '2222', '3333', '4444',
102
- '5555', '6666', '7777', '8888', '9999', 'password',
103
- 'admin', 'root', '123456', '654321', 'qwerty'
104
- ];
105
-
106
- return weakPatterns.some(pattern => {
107
- if (typeof pattern === 'string') {
108
- return pin.toLowerCase().includes(pattern);
109
- }
110
- return pattern.test(pin);
111
- });
112
- }
113
-
114
- /**
115
- * Create secure configuration file
116
- */
117
- createSecureConfig() {
118
- const config = this.generateSecureConfig();
119
- const validation = this.validateSecurityConfig(config);
120
-
121
- if (!validation.valid) {
122
- throw new Error(`Invalid security configuration: ${validation.errors.join(', ')}`);
123
- }
124
-
125
- // Ensure config directory exists
126
- const configDir = path.dirname(this.configPath);
127
- if (!SecurityUtils.safeExistsSync(configDir)) {
128
- fs.mkdirSync(configDir, { recursive: true });
129
- }
130
-
131
- // Remove actual secrets from config file (use env vars)
132
- const safeConfig = {
133
- ...config,
134
- secrets: {
135
- adminPin: config.secrets.adminPin ? '***' : null,
136
- encryptionKey: '***',
137
- jwtSecret: '***'
138
- }
139
- };
140
-
141
- SecurityUtils.safeWriteFileSync(this.configPath, JSON.stringify(safeConfig, null, 2));
142
-
143
- return {
144
- configPath: this.configPath,
145
- validation
146
- };
147
- }
148
-
149
- /**
150
- * Load and validate existing configuration
151
- */
152
- loadSecurityConfig() {
153
- if (!SecurityUtils.safeExistsSync(this.configPath)) {
154
- return this.createSecureConfig();
155
- }
156
-
157
- try {
158
- const config = JSON.parse(SecurityUtils.safeReadFileSync(this.configPath, path.dirname(this.configPath), 'utf8'));
159
- const validation = this.validateSecurityConfig(config);
160
-
161
- return {
162
- config,
163
- validation,
164
- configPath: this.configPath
165
- };
166
- } catch (error) {
167
- throw new Error(`Failed to load security configuration: ${error.message}`);
168
- }
169
- }
170
-
171
- /**
172
- * Rotate encryption keys (advanced operation)
173
- */
174
- rotateEncryptionKeys() {
175
- console.warn('⚠️ Key rotation is an advanced operation. Ensure you have backups.');
176
-
177
- const newKey = this.generateSecureKey();
178
- const timestamp = new Date().toISOString();
179
-
180
- // Create backup of old config
181
- if (SecurityUtils.safeExistsSync(this.configPath)) {
182
- fs.copyFileSync(this.configPath, `${this.configPath}.backup.${timestamp}`);
183
- }
184
-
185
- // Update configuration with new keys
186
- const config = this.generateSecureConfig();
187
- config.secrets.encryptionKey = newKey;
188
- config.secrets.jwtSecret = this.generateSecureKey();
189
- config.lastKeyRotation = timestamp;
190
-
191
- this.createSecureConfig();
192
-
193
- return {
194
- oldKeyBackup: `${this.configPath}.backup.${timestamp}`,
195
- newKeysGenerated: true,
196
- timestamp
197
- };
198
- }
199
- }
200
-
201
- // Export for benchmark usage
202
- async function validateConfiguration(config) {
203
- const validator = new SecurityConfig();
204
-
205
- // Simulate validation processing
206
- const start = Date.now();
207
-
208
- // Basic validation for benchmark purposes
209
- const errors = [];
210
-
211
- if (!config.languages || !Array.isArray(config.languages) || config.languages.length === 0) {
212
- errors.push('languages must be a non-empty array');
213
- }
214
-
215
- if (!config.sourceDir || typeof config.sourceDir !== 'string') {
216
- errors.push('sourceDir must be a string');
217
- }
218
-
219
- if (config.adminPin && typeof config.adminPin !== 'string') {
220
- errors.push('adminPin must be a string');
221
- }
222
-
223
- // Simulate processing delay based on config complexity
224
- const complexity = (config.languages?.length || 0) * 2;
225
- await new Promise(resolve => setTimeout(resolve, complexity));
226
-
227
- const end = Date.now();
228
-
229
- return {
230
- valid: errors.length === 0,
231
- errors,
232
- validationTime: end - start
233
- };
234
- }
235
-
236
- module.exports = {
237
- SecurityConfig,
238
- validateConfiguration
239
- };