@sparkleideas/security 3.0.0-alpha.7
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 +234 -0
- package/__tests__/acceptance/security-compliance.test.ts +674 -0
- package/__tests__/credential-generator.test.ts +310 -0
- package/__tests__/fixtures/configurations.ts +419 -0
- package/__tests__/fixtures/index.ts +21 -0
- package/__tests__/helpers/create-mock.ts +469 -0
- package/__tests__/helpers/index.ts +32 -0
- package/__tests__/input-validator.test.ts +381 -0
- package/__tests__/integration/security-flow.test.ts +606 -0
- package/__tests__/password-hasher.test.ts +239 -0
- package/__tests__/path-validator.test.ts +302 -0
- package/__tests__/safe-executor.test.ts +292 -0
- package/__tests__/token-generator.test.ts +371 -0
- package/__tests__/unit/credential-generator.test.ts +182 -0
- package/__tests__/unit/password-hasher.test.ts +359 -0
- package/__tests__/unit/path-validator.test.ts +509 -0
- package/__tests__/unit/safe-executor.test.ts +667 -0
- package/__tests__/unit/token-generator.test.ts +310 -0
- package/package.json +28 -0
- package/src/CVE-REMEDIATION.ts +251 -0
- package/src/application/index.ts +10 -0
- package/src/application/services/security-application-service.ts +193 -0
- package/src/credential-generator.ts +368 -0
- package/src/domain/entities/security-context.ts +173 -0
- package/src/domain/index.ts +17 -0
- package/src/domain/services/security-domain-service.ts +296 -0
- package/src/index.ts +271 -0
- package/src/input-validator.ts +466 -0
- package/src/password-hasher.ts +270 -0
- package/src/path-validator.ts +525 -0
- package/src/safe-executor.ts +525 -0
- package/src/token-generator.ts +463 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Claude-Flow Path Validator Unit Tests
|
|
3
|
+
*
|
|
4
|
+
* London School TDD - Behavior Verification
|
|
5
|
+
* Tests path validation for security (CVE-1, CVE-2 prevention)
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
8
|
+
import { createMock, type MockedInterface } from '../../helpers/create-mock';
|
|
9
|
+
import { securityConfigs } from '../../fixtures/configurations';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Path validator interface (to be implemented)
|
|
13
|
+
*/
|
|
14
|
+
interface IPathValidator {
|
|
15
|
+
isValid(path: string): boolean;
|
|
16
|
+
normalize(path: string): string;
|
|
17
|
+
isWithinAllowedDirectory(path: string): boolean;
|
|
18
|
+
sanitize(path: string): string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* File system provider interface (collaborator)
|
|
23
|
+
*/
|
|
24
|
+
interface IFileSystemProvider {
|
|
25
|
+
exists(path: string): Promise<boolean>;
|
|
26
|
+
isDirectory(path: string): Promise<boolean>;
|
|
27
|
+
realPath(path: string): Promise<string>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Path utility interface (collaborator)
|
|
32
|
+
*/
|
|
33
|
+
interface IPathUtils {
|
|
34
|
+
normalize(path: string): string;
|
|
35
|
+
resolve(basePath: string, relativePath: string): string;
|
|
36
|
+
isAbsolute(path: string): boolean;
|
|
37
|
+
dirname(path: string): string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Path validator implementation for testing
|
|
42
|
+
*/
|
|
43
|
+
class PathValidator implements IPathValidator {
|
|
44
|
+
constructor(
|
|
45
|
+
private readonly pathUtils: IPathUtils,
|
|
46
|
+
private readonly config: typeof securityConfigs.strict.paths
|
|
47
|
+
) {}
|
|
48
|
+
|
|
49
|
+
isValid(path: string): boolean {
|
|
50
|
+
if (!path || typeof path !== 'string') {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (path.length > this.config.maxPathLength) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check for blocked patterns
|
|
59
|
+
for (const pattern of this.config.blockedPatterns) {
|
|
60
|
+
if (path.includes(pattern)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check for null bytes
|
|
66
|
+
if (path.includes('\0')) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
normalize(path: string): string {
|
|
74
|
+
return this.pathUtils.normalize(path);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
isWithinAllowedDirectory(path: string): boolean {
|
|
78
|
+
const normalizedPath = this.pathUtils.normalize(path);
|
|
79
|
+
|
|
80
|
+
for (const allowedDir of this.config.allowedDirectories) {
|
|
81
|
+
const normalizedAllowed = this.pathUtils.normalize(allowedDir);
|
|
82
|
+
if (normalizedPath.startsWith(normalizedAllowed)) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
sanitize(path: string): string {
|
|
91
|
+
let sanitized = path;
|
|
92
|
+
|
|
93
|
+
// Remove blocked patterns
|
|
94
|
+
for (const pattern of this.config.blockedPatterns) {
|
|
95
|
+
sanitized = sanitized.split(pattern).join('');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Remove null bytes
|
|
99
|
+
sanitized = sanitized.replace(/\0/g, '');
|
|
100
|
+
|
|
101
|
+
// Truncate if too long
|
|
102
|
+
if (sanitized.length > this.config.maxPathLength) {
|
|
103
|
+
sanitized = sanitized.slice(0, this.config.maxPathLength);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return this.pathUtils.normalize(sanitized);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
describe('PathValidator', () => {
|
|
111
|
+
let mockPathUtils: MockedInterface<IPathUtils>;
|
|
112
|
+
let pathValidator: PathValidator;
|
|
113
|
+
const pathConfig = securityConfigs.strict.paths;
|
|
114
|
+
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
mockPathUtils = createMock<IPathUtils>();
|
|
117
|
+
|
|
118
|
+
// Configure default mock behavior
|
|
119
|
+
mockPathUtils.normalize.mockImplementation((p: string) => p.replace(/\/+/g, '/'));
|
|
120
|
+
mockPathUtils.resolve.mockImplementation((base: string, rel: string) => `${base}/${rel}`);
|
|
121
|
+
mockPathUtils.isAbsolute.mockReturnValue(false);
|
|
122
|
+
mockPathUtils.dirname.mockImplementation((p: string) => p.split('/').slice(0, -1).join('/'));
|
|
123
|
+
|
|
124
|
+
pathValidator = new PathValidator(mockPathUtils, pathConfig);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('isValid', () => {
|
|
128
|
+
it('should return false for empty path', () => {
|
|
129
|
+
// Given
|
|
130
|
+
const emptyPath = '';
|
|
131
|
+
|
|
132
|
+
// When
|
|
133
|
+
const result = pathValidator.isValid(emptyPath);
|
|
134
|
+
|
|
135
|
+
// Then
|
|
136
|
+
expect(result).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should return false for null path', () => {
|
|
140
|
+
// Given
|
|
141
|
+
const nullPath = null as unknown as string;
|
|
142
|
+
|
|
143
|
+
// When
|
|
144
|
+
const result = pathValidator.isValid(nullPath);
|
|
145
|
+
|
|
146
|
+
// Then
|
|
147
|
+
expect(result).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should return false for path exceeding max length', () => {
|
|
151
|
+
// Given
|
|
152
|
+
const longPath = 'a'.repeat(pathConfig.maxPathLength + 1);
|
|
153
|
+
|
|
154
|
+
// When
|
|
155
|
+
const result = pathValidator.isValid(longPath);
|
|
156
|
+
|
|
157
|
+
// Then
|
|
158
|
+
expect(result).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should return true for path at exactly max length', () => {
|
|
162
|
+
// Given
|
|
163
|
+
const exactPath = 'a'.repeat(pathConfig.maxPathLength);
|
|
164
|
+
|
|
165
|
+
// When
|
|
166
|
+
const result = pathValidator.isValid(exactPath);
|
|
167
|
+
|
|
168
|
+
// Then
|
|
169
|
+
expect(result).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should return false for path with directory traversal (../)', () => {
|
|
173
|
+
// Given - CVE-1 prevention
|
|
174
|
+
const traversalPath = '../../../etc/passwd';
|
|
175
|
+
|
|
176
|
+
// When
|
|
177
|
+
const result = pathValidator.isValid(traversalPath);
|
|
178
|
+
|
|
179
|
+
// Then
|
|
180
|
+
expect(result).toBe(false);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should return false for path with home directory reference (~/)', () => {
|
|
184
|
+
// Given - CVE-1 prevention
|
|
185
|
+
const homePath = '~/.ssh/id_rsa';
|
|
186
|
+
|
|
187
|
+
// When
|
|
188
|
+
const result = pathValidator.isValid(homePath);
|
|
189
|
+
|
|
190
|
+
// Then
|
|
191
|
+
expect(result).toBe(false);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should return false for path to /etc/', () => {
|
|
195
|
+
// Given - CVE-2 prevention
|
|
196
|
+
const etcPath = '/etc/shadow';
|
|
197
|
+
|
|
198
|
+
// When
|
|
199
|
+
const result = pathValidator.isValid(etcPath);
|
|
200
|
+
|
|
201
|
+
// Then
|
|
202
|
+
expect(result).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should return false for path to /tmp/', () => {
|
|
206
|
+
// Given
|
|
207
|
+
const tmpPath = '/tmp/malicious.sh';
|
|
208
|
+
|
|
209
|
+
// When
|
|
210
|
+
const result = pathValidator.isValid(tmpPath);
|
|
211
|
+
|
|
212
|
+
// Then
|
|
213
|
+
expect(result).toBe(false);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should return false for path with null bytes', () => {
|
|
217
|
+
// Given - Null byte injection prevention
|
|
218
|
+
const nullBytePath = 'file.txt\0.exe';
|
|
219
|
+
|
|
220
|
+
// When
|
|
221
|
+
const result = pathValidator.isValid(nullBytePath);
|
|
222
|
+
|
|
223
|
+
// Then
|
|
224
|
+
expect(result).toBe(false);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should return true for valid relative path', () => {
|
|
228
|
+
// Given
|
|
229
|
+
const validPath = 'src/modules/security/index.ts';
|
|
230
|
+
|
|
231
|
+
// When
|
|
232
|
+
const result = pathValidator.isValid(validPath);
|
|
233
|
+
|
|
234
|
+
// Then
|
|
235
|
+
expect(result).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should return true for path within allowed directory', () => {
|
|
239
|
+
// Given
|
|
240
|
+
const validPath = './v3/src/security/hasher.ts';
|
|
241
|
+
|
|
242
|
+
// When
|
|
243
|
+
const result = pathValidator.isValid(validPath);
|
|
244
|
+
|
|
245
|
+
// Then
|
|
246
|
+
expect(result).toBe(true);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('normalize', () => {
|
|
251
|
+
it('should delegate to path utils for normalization', () => {
|
|
252
|
+
// Given
|
|
253
|
+
const path = './src/../src/file.ts';
|
|
254
|
+
mockPathUtils.normalize.mockReturnValue('/normalized/path');
|
|
255
|
+
|
|
256
|
+
// When
|
|
257
|
+
const result = pathValidator.normalize(path);
|
|
258
|
+
|
|
259
|
+
// Then
|
|
260
|
+
expect(mockPathUtils.normalize).toHaveBeenCalledWith(path);
|
|
261
|
+
expect(result).toBe('/normalized/path');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should normalize multiple slashes', () => {
|
|
265
|
+
// Given
|
|
266
|
+
const path = 'src///modules//security//index.ts';
|
|
267
|
+
|
|
268
|
+
// When
|
|
269
|
+
const result = pathValidator.normalize(path);
|
|
270
|
+
|
|
271
|
+
// Then
|
|
272
|
+
expect(mockPathUtils.normalize).toHaveBeenCalledWith(path);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('isWithinAllowedDirectory', () => {
|
|
277
|
+
it('should return true for path within ./v3/', () => {
|
|
278
|
+
// Given
|
|
279
|
+
const path = './v3/src/security/hasher.ts';
|
|
280
|
+
mockPathUtils.normalize.mockImplementation((p: string) => p);
|
|
281
|
+
|
|
282
|
+
// When
|
|
283
|
+
const result = pathValidator.isWithinAllowedDirectory(path);
|
|
284
|
+
|
|
285
|
+
// Then
|
|
286
|
+
expect(result).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should return true for path within ./src/', () => {
|
|
290
|
+
// Given
|
|
291
|
+
const path = './src/modules/core/index.ts';
|
|
292
|
+
mockPathUtils.normalize.mockImplementation((p: string) => p);
|
|
293
|
+
|
|
294
|
+
// When
|
|
295
|
+
const result = pathValidator.isWithinAllowedDirectory(path);
|
|
296
|
+
|
|
297
|
+
// Then
|
|
298
|
+
expect(result).toBe(true);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('should return true for path within ./tests/', () => {
|
|
302
|
+
// Given
|
|
303
|
+
const path = './tests/unit/security.test.ts';
|
|
304
|
+
mockPathUtils.normalize.mockImplementation((p: string) => p);
|
|
305
|
+
|
|
306
|
+
// When
|
|
307
|
+
const result = pathValidator.isWithinAllowedDirectory(path);
|
|
308
|
+
|
|
309
|
+
// Then
|
|
310
|
+
expect(result).toBe(true);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('should return false for path outside allowed directories', () => {
|
|
314
|
+
// Given
|
|
315
|
+
const path = '/usr/local/bin/malicious';
|
|
316
|
+
mockPathUtils.normalize.mockImplementation((p: string) => p);
|
|
317
|
+
|
|
318
|
+
// When
|
|
319
|
+
const result = pathValidator.isWithinAllowedDirectory(path);
|
|
320
|
+
|
|
321
|
+
// Then
|
|
322
|
+
expect(result).toBe(false);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should normalize path before checking', () => {
|
|
326
|
+
// Given
|
|
327
|
+
const path = './v3/../v3/src/file.ts';
|
|
328
|
+
mockPathUtils.normalize.mockReturnValue('./v3/src/file.ts');
|
|
329
|
+
|
|
330
|
+
// When
|
|
331
|
+
pathValidator.isWithinAllowedDirectory(path);
|
|
332
|
+
|
|
333
|
+
// Then
|
|
334
|
+
expect(mockPathUtils.normalize).toHaveBeenCalledWith(path);
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
describe('sanitize', () => {
|
|
339
|
+
it('should remove directory traversal patterns', () => {
|
|
340
|
+
// Given
|
|
341
|
+
const unsafePath = '../../../etc/passwd';
|
|
342
|
+
|
|
343
|
+
// When
|
|
344
|
+
const result = pathValidator.sanitize(unsafePath);
|
|
345
|
+
|
|
346
|
+
// Then
|
|
347
|
+
expect(result).not.toContain('../');
|
|
348
|
+
expect(mockPathUtils.normalize).toHaveBeenCalled();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('should remove home directory references', () => {
|
|
352
|
+
// Given
|
|
353
|
+
const unsafePath = '~/.ssh/id_rsa';
|
|
354
|
+
|
|
355
|
+
// When
|
|
356
|
+
const result = pathValidator.sanitize(unsafePath);
|
|
357
|
+
|
|
358
|
+
// Then
|
|
359
|
+
expect(result).not.toContain('~/');
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('should remove /etc/ references', () => {
|
|
363
|
+
// Given
|
|
364
|
+
const unsafePath = 'some/path/etc/passwd';
|
|
365
|
+
|
|
366
|
+
// When
|
|
367
|
+
const result = pathValidator.sanitize(unsafePath);
|
|
368
|
+
|
|
369
|
+
// Then
|
|
370
|
+
expect(result).not.toContain('/etc/');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('should remove null bytes', () => {
|
|
374
|
+
// Given
|
|
375
|
+
const unsafePath = 'file.txt\0.exe';
|
|
376
|
+
|
|
377
|
+
// When
|
|
378
|
+
const result = pathValidator.sanitize(unsafePath);
|
|
379
|
+
|
|
380
|
+
// Then
|
|
381
|
+
expect(result).not.toContain('\0');
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('should truncate paths exceeding max length', () => {
|
|
385
|
+
// Given
|
|
386
|
+
const longPath = 'a'.repeat(pathConfig.maxPathLength + 100);
|
|
387
|
+
|
|
388
|
+
// When
|
|
389
|
+
const result = pathValidator.sanitize(longPath);
|
|
390
|
+
|
|
391
|
+
// Then
|
|
392
|
+
expect(result.length).toBeLessThanOrEqual(pathConfig.maxPathLength);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('should normalize the sanitized path', () => {
|
|
396
|
+
// Given
|
|
397
|
+
const path = 'valid/path/file.ts';
|
|
398
|
+
|
|
399
|
+
// When
|
|
400
|
+
pathValidator.sanitize(path);
|
|
401
|
+
|
|
402
|
+
// Then
|
|
403
|
+
expect(mockPathUtils.normalize).toHaveBeenCalled();
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
describe('CVE prevention scenarios', () => {
|
|
408
|
+
it('should block CVE-1: directory traversal attack', () => {
|
|
409
|
+
// Given - Various traversal attempts
|
|
410
|
+
const attacks = [
|
|
411
|
+
'../../../etc/passwd',
|
|
412
|
+
'..\\..\\..\\Windows\\System32\\config\\SAM',
|
|
413
|
+
'valid/path/../../../etc/passwd',
|
|
414
|
+
'....//....//....//etc/passwd',
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
// When/Then
|
|
418
|
+
for (const attack of attacks) {
|
|
419
|
+
expect(pathValidator.isValid(attack)).toBe(false);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should block CVE-2: absolute path injection', () => {
|
|
424
|
+
// Given - Absolute path attempts
|
|
425
|
+
const attacks = [
|
|
426
|
+
'/etc/passwd',
|
|
427
|
+
'/var/log/auth.log',
|
|
428
|
+
'/tmp/malicious',
|
|
429
|
+
];
|
|
430
|
+
|
|
431
|
+
// When/Then
|
|
432
|
+
for (const attack of attacks) {
|
|
433
|
+
expect(pathValidator.isValid(attack)).toBe(false);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('should block CVE-3: null byte injection', () => {
|
|
438
|
+
// Given - Null byte attempts
|
|
439
|
+
const attacks = [
|
|
440
|
+
'file.txt\0',
|
|
441
|
+
'image.png\0.exe',
|
|
442
|
+
'\0/etc/passwd',
|
|
443
|
+
];
|
|
444
|
+
|
|
445
|
+
// When/Then
|
|
446
|
+
for (const attack of attacks) {
|
|
447
|
+
expect(pathValidator.isValid(attack)).toBe(false);
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('should block encoded traversal attempts', () => {
|
|
452
|
+
// Given - URL encoded traversal
|
|
453
|
+
const path = '%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd';
|
|
454
|
+
|
|
455
|
+
// When - After decoding, should still be blocked
|
|
456
|
+
const decoded = decodeURIComponent(path);
|
|
457
|
+
const result = pathValidator.isValid(decoded);
|
|
458
|
+
|
|
459
|
+
// Then
|
|
460
|
+
expect(result).toBe(false);
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
describe('edge cases', () => {
|
|
465
|
+
it('should handle Windows-style paths', () => {
|
|
466
|
+
// Given
|
|
467
|
+
const windowsPath = 'C:\\Users\\test\\file.txt';
|
|
468
|
+
|
|
469
|
+
// When
|
|
470
|
+
const result = pathValidator.isValid(windowsPath);
|
|
471
|
+
|
|
472
|
+
// Then - Windows paths should be evaluated based on patterns
|
|
473
|
+
expect(typeof result).toBe('boolean');
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('should handle unicode in paths', () => {
|
|
477
|
+
// Given
|
|
478
|
+
const unicodePath = 'src/modules/\u0000.ts';
|
|
479
|
+
|
|
480
|
+
// When
|
|
481
|
+
const result = pathValidator.isValid(unicodePath);
|
|
482
|
+
|
|
483
|
+
// Then
|
|
484
|
+
expect(result).toBe(false); // Contains null character
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('should handle paths with only dots', () => {
|
|
488
|
+
// Given
|
|
489
|
+
const dotsPath = '...';
|
|
490
|
+
|
|
491
|
+
// When
|
|
492
|
+
const result = pathValidator.isValid(dotsPath);
|
|
493
|
+
|
|
494
|
+
// Then
|
|
495
|
+
expect(result).toBe(true); // Not a traversal pattern
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('should handle paths with special characters', () => {
|
|
499
|
+
// Given
|
|
500
|
+
const specialPath = 'src/file-name_v2.0.1.ts';
|
|
501
|
+
|
|
502
|
+
// When
|
|
503
|
+
const result = pathValidator.isValid(specialPath);
|
|
504
|
+
|
|
505
|
+
// Then
|
|
506
|
+
expect(result).toBe(true);
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
});
|