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.
- package/README.md +10 -6
- package/main/i18ntk-backup-class.js +35 -423
- package/main/manage/commands/BackupCommand.js +62 -62
- package/main/manage/commands/FixerCommand.js +97 -97
- package/main/manage/managers/DebugMenu.js +10 -9
- package/main/manage/services/SetupService.js +444 -462
- package/package.json +61 -32
- package/runtime/index.js +14 -8
- package/utils/admin-auth.js +594 -576
- package/utils/config-manager.js +72 -72
- package/utils/env-manager.js +117 -26
- package/utils/i18n-helper.js +50 -49
- package/utils/json-output.js +13 -12
- package/utils/logger.js +7 -6
- package/utils/npm-version-warning.js +12 -141
- package/utils/prompt-helper.js +44 -41
- package/utils/secure-errors.js +156 -154
- package/utils/security.js +235 -233
- package/utils/setup-enforcer.js +110 -109
- package/utils/terminal-icons.js +164 -163
- package/settings/i18ntk-config.json +0 -283
- package/utils/admin-pin.js +0 -520
- package/utils/arg-parser.js +0 -40
- package/utils/cli-args.js +0 -210
- package/utils/mini-commander.js +0 -179
- package/utils/missing-key-validator.js +0 -858
- package/utils/path-utils.js +0 -33
- package/utils/performance-optimizer.js +0 -246
- package/utils/prompt-new.js +0 -55
- package/utils/promptPin.js +0 -76
- package/utils/safe-json.js +0 -40
- package/utils/secure-backup.js +0 -340
- package/utils/security-check-improved.js +0 -393
- package/utils/security-config.js +0 -239
- package/utils/setup-validator.js +0 -717
- package/utils/ultra-performance-optimizer.js +0 -352
package/utils/security.js
CHANGED
|
@@ -1,93 +1,94 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const crypto = require('crypto');
|
|
4
|
-
const { logger } = require('./logger');
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
findPackageRoot(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
.
|
|
47
|
-
.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
if (
|
|
83
|
-
if (
|
|
84
|
-
if (
|
|
85
|
-
return
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const { logger } = require('./logger');
|
|
5
|
+
const { envManager } = require('./env-manager');
|
|
6
|
+
|
|
7
|
+
const INTERNAL_MANIFEST_CACHE = {
|
|
8
|
+
initialized: false,
|
|
9
|
+
roots: new Set()
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function findPackageRoot(startDir) {
|
|
13
|
+
let current = path.resolve(startDir || process.cwd());
|
|
14
|
+
while (true) {
|
|
15
|
+
const manifest = path.join(current, 'package.json');
|
|
16
|
+
if (fs.existsSync(manifest)) {
|
|
17
|
+
return current;
|
|
18
|
+
}
|
|
19
|
+
const parent = path.dirname(current);
|
|
20
|
+
if (parent === current) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
current = parent;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function initializeInternalRoots() {
|
|
28
|
+
if (INTERNAL_MANIFEST_CACHE.initialized) {
|
|
29
|
+
return INTERNAL_MANIFEST_CACHE.roots;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
INTERNAL_MANIFEST_CACHE.initialized = true;
|
|
33
|
+
const roots = INTERNAL_MANIFEST_CACHE.roots;
|
|
34
|
+
const candidates = [
|
|
35
|
+
process.cwd(),
|
|
36
|
+
path.resolve(__dirname, '..'),
|
|
37
|
+
findPackageRoot(process.cwd()),
|
|
38
|
+
findPackageRoot(path.resolve(__dirname, '..'))
|
|
39
|
+
].filter(Boolean);
|
|
40
|
+
|
|
41
|
+
for (const candidate of candidates) {
|
|
42
|
+
roots.add(path.resolve(candidate));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const custom = String(envManager.get('I18NTK_INTERNAL_PATH_PREFIXES') || '')
|
|
46
|
+
.split(',')
|
|
47
|
+
.map((entry) => entry.trim())
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
for (const prefix of custom) {
|
|
50
|
+
roots.add(path.resolve(prefix));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return roots;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeForCompare(value) {
|
|
57
|
+
return String(value || '').replace(/\\/g, '/').toLowerCase();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isPathInside(root, target) {
|
|
61
|
+
const normalizedRoot = normalizeForCompare(path.resolve(root));
|
|
62
|
+
const normalizedTarget = normalizeForCompare(path.resolve(target));
|
|
63
|
+
return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(`${normalizedRoot}/`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isInternalPath(inputPath) {
|
|
67
|
+
if (!inputPath || typeof inputPath !== 'string') {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const roots = initializeInternalRoots();
|
|
71
|
+
const absolute = path.resolve(inputPath);
|
|
72
|
+
for (const root of roots) {
|
|
73
|
+
if (isPathInside(root, absolute)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function detectDangerReason(filePath) {
|
|
81
|
+
if (/\.\.(\/|\\|$)/.test(filePath)) return 'Contains parent directory traversal segments';
|
|
82
|
+
if (/(^|[\/\\])~([\/\\]|$)/.test(filePath)) return 'Contains home-directory shorthand';
|
|
83
|
+
if (/\$\{/.test(filePath)) return 'Contains variable expansion token';
|
|
84
|
+
if (/`/.test(filePath)) return 'Contains command substitution token';
|
|
85
|
+
if (/[|;&<>]/.test(filePath)) return 'Contains shell metacharacters';
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
// Lazy load i18n to prevent initialization race conditions
|
|
91
|
+
let i18n;
|
|
91
92
|
function getI18n() {
|
|
92
93
|
if (!i18n) {
|
|
93
94
|
try {
|
|
@@ -141,13 +142,13 @@ static _logging = false;
|
|
|
141
142
|
SecurityUtils._operationStack = new Set();
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
if (SecurityUtils._operationStack.has(operationName)) {
|
|
145
|
-
SecurityUtils.logSecurityEvent('Recursion detected while performing secure operation', 'warn', {
|
|
146
|
-
operation: operationName,
|
|
147
|
-
source: 'internal'
|
|
148
|
-
});
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
145
|
+
if (SecurityUtils._operationStack.has(operationName)) {
|
|
146
|
+
SecurityUtils.logSecurityEvent('Recursion detected while performing secure operation', 'warn', {
|
|
147
|
+
operation: operationName,
|
|
148
|
+
source: 'internal'
|
|
149
|
+
});
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
151
152
|
|
|
152
153
|
SecurityUtils._operationStack.add(operationName);
|
|
153
154
|
|
|
@@ -157,15 +158,15 @@ static _logging = false;
|
|
|
157
158
|
let hasResult = false;
|
|
158
159
|
let timeoutId = null;
|
|
159
160
|
|
|
160
|
-
timeoutId = setTimeout(() => {
|
|
161
|
-
if (!hasResult) {
|
|
162
|
-
SecurityUtils.logSecurityEvent('Secure operation timeout', 'warn', {
|
|
163
|
-
operation: operationName,
|
|
164
|
-
timeoutMs,
|
|
165
|
-
source: 'internal'
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}, timeoutMs);
|
|
161
|
+
timeoutId = setTimeout(() => {
|
|
162
|
+
if (!hasResult) {
|
|
163
|
+
SecurityUtils.logSecurityEvent('Secure operation timeout', 'warn', {
|
|
164
|
+
operation: operationName,
|
|
165
|
+
timeoutMs,
|
|
166
|
+
source: 'internal'
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}, timeoutMs);
|
|
169
170
|
|
|
170
171
|
// Execute operation synchronously
|
|
171
172
|
result = operation();
|
|
@@ -177,12 +178,12 @@ static _logging = false;
|
|
|
177
178
|
|
|
178
179
|
return result;
|
|
179
180
|
} catch (error) {
|
|
180
|
-
SecurityUtils.logSecurityEvent('Secure operation error', 'error', {
|
|
181
|
-
operation: operationName,
|
|
182
|
-
error: error.message,
|
|
183
|
-
source: 'internal'
|
|
184
|
-
});
|
|
185
|
-
return null;
|
|
181
|
+
SecurityUtils.logSecurityEvent('Secure operation error', 'error', {
|
|
182
|
+
operation: operationName,
|
|
183
|
+
error: error.message,
|
|
184
|
+
source: 'internal'
|
|
185
|
+
});
|
|
186
|
+
return null;
|
|
186
187
|
} finally {
|
|
187
188
|
SecurityUtils._operationStack.delete(operationName);
|
|
188
189
|
}
|
|
@@ -194,40 +195,40 @@ static _logging = false;
|
|
|
194
195
|
* @param {string} level - Log level (info, warn, error)
|
|
195
196
|
* @param {object} details - Additional details
|
|
196
197
|
*/
|
|
197
|
-
static logSecurityEvent(event, level = 'info', details = {}) {
|
|
198
|
-
if (SecurityUtils._logging) {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
SecurityUtils._logging = true;
|
|
203
|
-
try {
|
|
204
|
-
const debugMode = logger.isDebugMode();
|
|
205
|
-
const explicitSecurityLogs =
|
|
206
|
-
const source = details && details.source ? String(details.source).toLowerCase() : 'internal';
|
|
207
|
-
const levelName = String(level || 'info').toLowerCase();
|
|
208
|
-
const normalizedLevel = levelName === 'warning' ? 'warn' : levelName;
|
|
209
|
-
|
|
210
|
-
// Security warnings from internal paths are noise during builds.
|
|
211
|
-
if (!debugMode && !explicitSecurityLogs && source !== 'user') {
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
logger.security(normalizedLevel, event, {
|
|
216
|
-
...details,
|
|
217
|
-
pid: process.pid,
|
|
218
|
-
nodeVersion: process.version
|
|
219
|
-
});
|
|
220
|
-
} finally {
|
|
221
|
-
SecurityUtils._logging = false;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
198
|
+
static logSecurityEvent(event, level = 'info', details = {}) {
|
|
199
|
+
if (SecurityUtils._logging) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
SecurityUtils._logging = true;
|
|
204
|
+
try {
|
|
205
|
+
const debugMode = logger.isDebugMode();
|
|
206
|
+
const explicitSecurityLogs = envManager.getBoolean('I18NTK_ENABLE_SECURITY_LOGS');
|
|
207
|
+
const source = details && details.source ? String(details.source).toLowerCase() : 'internal';
|
|
208
|
+
const levelName = String(level || 'info').toLowerCase();
|
|
209
|
+
const normalizedLevel = levelName === 'warning' ? 'warn' : levelName;
|
|
210
|
+
|
|
211
|
+
// Security warnings from internal paths are noise during builds.
|
|
212
|
+
if (!debugMode && !explicitSecurityLogs && source !== 'user') {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
logger.security(normalizedLevel, event, {
|
|
217
|
+
...details,
|
|
218
|
+
pid: process.pid,
|
|
219
|
+
nodeVersion: process.version
|
|
220
|
+
});
|
|
221
|
+
} finally {
|
|
222
|
+
SecurityUtils._logging = false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
224
225
|
|
|
225
226
|
// Add other static methods here...
|
|
226
|
-
static validatePath(filePath, basePath = process.cwd(), verbose = false) {
|
|
227
|
-
const i18n = getI18n();
|
|
228
|
-
const useI18n = i18n && i18n.isInitialized && typeof i18n.t === 'function';
|
|
229
|
-
|
|
230
|
-
try {
|
|
227
|
+
static validatePath(filePath, basePath = process.cwd(), verbose = false) {
|
|
228
|
+
const i18n = getI18n();
|
|
229
|
+
const useI18n = i18n && i18n.isInitialized && typeof i18n.t === 'function';
|
|
230
|
+
|
|
231
|
+
try {
|
|
231
232
|
// Check against whitelist patterns for our own package artifacts
|
|
232
233
|
if (SecurityUtils.PACKAGE_ARTIFACT_WHITELIST.some(pattern => pattern.test(filePath))) {
|
|
233
234
|
return filePath;
|
|
@@ -247,42 +248,42 @@ static _logging = false;
|
|
|
247
248
|
return null;
|
|
248
249
|
}
|
|
249
250
|
|
|
250
|
-
const isWindowsAbsolute = /^[A-Z]:[\/\\]/i.test(filePath);
|
|
251
|
-
const isUnixAbsolute = filePath.startsWith('/') || filePath.startsWith('\\');
|
|
252
|
-
const isAbsolutePath = isWindowsAbsolute || isUnixAbsolute;
|
|
253
|
-
const dangerousReason = detectDangerReason(filePath);
|
|
254
|
-
const source = isInternalPath(filePath) ? 'internal' : 'user';
|
|
255
|
-
|
|
256
|
-
if (isAbsolutePath && isInternalPath(filePath)) {
|
|
257
|
-
return path.resolve(filePath);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// For absolute paths, defer trust decision to base-path containment checks below,
|
|
261
|
-
// but still reject obvious shell/injection markers.
|
|
262
|
-
if (isAbsolutePath && dangerousReason) {
|
|
263
|
-
const message = useI18n
|
|
264
|
-
? i18n.t('security.pathTraversalAttempt')
|
|
265
|
-
: 'Path traversal attempt';
|
|
266
|
-
SecurityUtils.logSecurityEvent(message, 'warning', {
|
|
267
|
-
inputPath: filePath,
|
|
268
|
-
reason: dangerousReason,
|
|
269
|
-
source
|
|
270
|
-
});
|
|
271
|
-
return null;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Check for obvious dangerous patterns first for relative paths
|
|
275
|
-
if (!isAbsolutePath && !SecurityUtils.isSafePath(filePath)) {
|
|
276
|
-
const message = useI18n
|
|
277
|
-
? i18n.t('security.pathTraversalAttempt')
|
|
278
|
-
: 'Path traversal attempt';
|
|
279
|
-
SecurityUtils.logSecurityEvent(message, 'warning', {
|
|
280
|
-
inputPath: filePath,
|
|
281
|
-
reason: dangerousReason || 'Contains unsafe path segments',
|
|
282
|
-
source
|
|
283
|
-
});
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
251
|
+
const isWindowsAbsolute = /^[A-Z]:[\/\\]/i.test(filePath);
|
|
252
|
+
const isUnixAbsolute = filePath.startsWith('/') || filePath.startsWith('\\');
|
|
253
|
+
const isAbsolutePath = isWindowsAbsolute || isUnixAbsolute;
|
|
254
|
+
const dangerousReason = detectDangerReason(filePath);
|
|
255
|
+
const source = isInternalPath(filePath) ? 'internal' : 'user';
|
|
256
|
+
|
|
257
|
+
if (isAbsolutePath && isInternalPath(filePath)) {
|
|
258
|
+
return path.resolve(filePath);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// For absolute paths, defer trust decision to base-path containment checks below,
|
|
262
|
+
// but still reject obvious shell/injection markers.
|
|
263
|
+
if (isAbsolutePath && dangerousReason) {
|
|
264
|
+
const message = useI18n
|
|
265
|
+
? i18n.t('security.pathTraversalAttempt')
|
|
266
|
+
: 'Path traversal attempt';
|
|
267
|
+
SecurityUtils.logSecurityEvent(message, 'warning', {
|
|
268
|
+
inputPath: filePath,
|
|
269
|
+
reason: dangerousReason,
|
|
270
|
+
source
|
|
271
|
+
});
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Check for obvious dangerous patterns first for relative paths
|
|
276
|
+
if (!isAbsolutePath && !SecurityUtils.isSafePath(filePath)) {
|
|
277
|
+
const message = useI18n
|
|
278
|
+
? i18n.t('security.pathTraversalAttempt')
|
|
279
|
+
: 'Path traversal attempt';
|
|
280
|
+
SecurityUtils.logSecurityEvent(message, 'warning', {
|
|
281
|
+
inputPath: filePath,
|
|
282
|
+
reason: dangerousReason || 'Contains unsafe path segments',
|
|
283
|
+
source
|
|
284
|
+
});
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
286
287
|
|
|
287
288
|
// Resolve base and target paths
|
|
288
289
|
const base = fs.realpathSync(basePath);
|
|
@@ -297,47 +298,47 @@ static _logging = false;
|
|
|
297
298
|
}
|
|
298
299
|
|
|
299
300
|
// Check for actual path traversal (going outside the base directory)
|
|
300
|
-
const relativePath = path.relative(base, finalPath);
|
|
301
|
-
if (relativePath.startsWith(
|
|
302
|
-
const message = useI18n
|
|
303
|
-
? i18n.t('security.pathTraversalAttempt')
|
|
304
|
-
: 'Path traversal attempt';
|
|
305
|
-
SecurityUtils.logSecurityEvent(message, 'warning', {
|
|
306
|
-
inputPath: filePath,
|
|
307
|
-
resolvedPath: finalPath,
|
|
308
|
-
basePath: base,
|
|
309
|
-
relativePath: relativePath,
|
|
310
|
-
source
|
|
311
|
-
});
|
|
312
|
-
return null;
|
|
313
|
-
}
|
|
301
|
+
const relativePath = path.relative(base, finalPath);
|
|
302
|
+
if (relativePath === '..' || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) {
|
|
303
|
+
const message = useI18n
|
|
304
|
+
? i18n.t('security.pathTraversalAttempt')
|
|
305
|
+
: 'Path traversal attempt';
|
|
306
|
+
SecurityUtils.logSecurityEvent(message, 'warning', {
|
|
307
|
+
inputPath: filePath,
|
|
308
|
+
resolvedPath: finalPath,
|
|
309
|
+
basePath: base,
|
|
310
|
+
relativePath: relativePath,
|
|
311
|
+
source
|
|
312
|
+
});
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
314
315
|
|
|
315
316
|
// Allow absolute paths that resolve within the project structure
|
|
316
317
|
// The isSafePath check above already filtered out dangerous absolute paths
|
|
317
318
|
|
|
318
319
|
if (verbose) {
|
|
319
|
-
const successMsg = useI18n
|
|
320
|
-
? i18n.t('security.pathValidated')
|
|
321
|
-
: 'Path validated';
|
|
322
|
-
SecurityUtils.logSecurityEvent(successMsg, 'info', {
|
|
323
|
-
inputPath: filePath,
|
|
324
|
-
resolvedPath: finalPath,
|
|
325
|
-
source
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
return finalPath;
|
|
320
|
+
const successMsg = useI18n
|
|
321
|
+
? i18n.t('security.pathValidated')
|
|
322
|
+
: 'Path validated';
|
|
323
|
+
SecurityUtils.logSecurityEvent(successMsg, 'info', {
|
|
324
|
+
inputPath: filePath,
|
|
325
|
+
resolvedPath: finalPath,
|
|
326
|
+
source
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return finalPath;
|
|
329
330
|
} catch (error) {
|
|
330
331
|
const message = useI18n
|
|
331
332
|
? i18n.t('security.pathValidationError')
|
|
332
333
|
: 'Path validation error';
|
|
333
|
-
SecurityUtils.logSecurityEvent(message, 'error', {
|
|
334
|
-
inputPath: filePath,
|
|
335
|
-
error: error.message,
|
|
336
|
-
source: isInternalPath(filePath) ? 'internal' : 'user'
|
|
337
|
-
});
|
|
338
|
-
return null;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
334
|
+
SecurityUtils.logSecurityEvent(message, 'error', {
|
|
335
|
+
inputPath: filePath,
|
|
336
|
+
error: error.message,
|
|
337
|
+
source: isInternalPath(filePath) ? 'internal' : 'user'
|
|
338
|
+
});
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
341
342
|
|
|
342
343
|
static safeExistsSync(filePath, basePath, timeoutMs = 3000) {
|
|
343
344
|
return this.withTimeoutSync(() => {
|
|
@@ -567,25 +568,26 @@ static _logging = false;
|
|
|
567
568
|
const resolvedBase = path.resolve(basePath);
|
|
568
569
|
const joinedPath = path.join(resolvedBase, ...paths);
|
|
569
570
|
const resolvedPath = path.resolve(joinedPath);
|
|
571
|
+
const relativePath = path.relative(resolvedBase, resolvedPath);
|
|
570
572
|
|
|
571
573
|
// Ensure the final path is within the base directory
|
|
572
|
-
if (
|
|
573
|
-
SecurityUtils.logSecurityEvent('Path traversal attempt detected in safeJoin', 'error', {
|
|
574
|
-
basePath,
|
|
575
|
-
paths,
|
|
576
|
-
resolvedPath,
|
|
577
|
-
source: 'internal'
|
|
578
|
-
});
|
|
579
|
-
return false;
|
|
580
|
-
}
|
|
574
|
+
if (relativePath === '..' || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) {
|
|
575
|
+
SecurityUtils.logSecurityEvent('Path traversal attempt detected in safeJoin', 'error', {
|
|
576
|
+
basePath,
|
|
577
|
+
paths,
|
|
578
|
+
resolvedPath,
|
|
579
|
+
source: 'internal'
|
|
580
|
+
});
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
581
583
|
return resolvedPath;
|
|
582
584
|
} catch (error) {
|
|
583
|
-
SecurityUtils.logSecurityEvent('Error in safeJoin', 'error', {
|
|
584
|
-
basePath,
|
|
585
|
-
paths,
|
|
586
|
-
error: error.message,
|
|
587
|
-
source: 'internal'
|
|
588
|
-
});
|
|
585
|
+
SecurityUtils.logSecurityEvent('Error in safeJoin', 'error', {
|
|
586
|
+
basePath,
|
|
587
|
+
paths,
|
|
588
|
+
error: error.message,
|
|
589
|
+
source: 'internal'
|
|
590
|
+
});
|
|
589
591
|
return false;
|
|
590
592
|
}
|
|
591
593
|
}
|
|
@@ -633,12 +635,12 @@ static _logging = false;
|
|
|
633
635
|
];
|
|
634
636
|
|
|
635
637
|
// Allow absolute paths that are within the project structure
|
|
636
|
-
if (filePath.startsWith('/') || filePath.startsWith('\\')) {
|
|
637
|
-
// Treat raw Unix-style absolute input as dangerous by default in this helper.
|
|
638
|
-
// `validatePath` can still permit absolute paths if they resolve within basePath.
|
|
639
|
-
const hasDangerousPatterns = dangerousPatterns.slice(1).some(pattern => pattern.test(filePath));
|
|
640
|
-
return !hasDangerousPatterns;
|
|
641
|
-
}
|
|
638
|
+
if (filePath.startsWith('/') || filePath.startsWith('\\')) {
|
|
639
|
+
// Treat raw Unix-style absolute input as dangerous by default in this helper.
|
|
640
|
+
// `validatePath` can still permit absolute paths if they resolve within basePath.
|
|
641
|
+
const hasDangerousPatterns = dangerousPatterns.slice(1).some(pattern => pattern.test(filePath));
|
|
642
|
+
return !hasDangerousPatterns;
|
|
643
|
+
}
|
|
642
644
|
|
|
643
645
|
return !dangerousPatterns.some(pattern => pattern.test(filePath));
|
|
644
646
|
}
|