#
tokens: 12102/50000 1/76 files (page 5/5)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 5 of 5. Use http://codebase.md/modelcontextprotocol/servers?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .gitattributes
├── .github
│   ├── pull_request_template.md
│   └── workflows
│       ├── claude.yml
│       ├── python.yml
│       ├── release.yml
│       └── typescript.yml
├── .gitignore
├── .npmrc
├── .vscode
│   └── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   └── release.py
├── SECURITY.md
├── src
│   ├── everything
│   │   ├── CLAUDE.md
│   │   ├── Dockerfile
│   │   ├── everything.ts
│   │   ├── index.ts
│   │   ├── instructions.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── sse.ts
│   │   ├── stdio.ts
│   │   ├── streamableHttp.ts
│   │   └── tsconfig.json
│   ├── fetch
│   │   ├── .python-version
│   │   ├── Dockerfile
│   │   ├── LICENSE
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   ├── src
│   │   │   └── mcp_server_fetch
│   │   │       ├── __init__.py
│   │   │       ├── __main__.py
│   │   │       └── server.py
│   │   └── uv.lock
│   ├── filesystem
│   │   ├── __tests__
│   │   │   ├── directory-tree.test.ts
│   │   │   ├── lib.test.ts
│   │   │   ├── path-utils.test.ts
│   │   │   ├── path-validation.test.ts
│   │   │   └── roots-utils.test.ts
│   │   ├── Dockerfile
│   │   ├── index.ts
│   │   ├── jest.config.cjs
│   │   ├── lib.ts
│   │   ├── package.json
│   │   ├── path-utils.ts
│   │   ├── path-validation.ts
│   │   ├── README.md
│   │   ├── roots-utils.ts
│   │   └── tsconfig.json
│   ├── git
│   │   ├── .gitignore
│   │   ├── .python-version
│   │   ├── Dockerfile
│   │   ├── LICENSE
│   │   ├── pyproject.toml
│   │   ├── README.md
│   │   ├── src
│   │   │   └── mcp_server_git
│   │   │       ├── __init__.py
│   │   │       ├── __main__.py
│   │   │       ├── py.typed
│   │   │       └── server.py
│   │   ├── tests
│   │   │   └── test_server.py
│   │   └── uv.lock
│   ├── memory
│   │   ├── Dockerfile
│   │   ├── index.ts
│   │   ├── package.json
│   │   ├── README.md
│   │   └── tsconfig.json
│   ├── sequentialthinking
│   │   ├── Dockerfile
│   │   ├── index.ts
│   │   ├── package.json
│   │   ├── README.md
│   │   └── tsconfig.json
│   └── time
│       ├── .python-version
│       ├── Dockerfile
│       ├── pyproject.toml
│       ├── README.md
│       ├── src
│       │   └── mcp_server_time
│       │       ├── __init__.py
│       │       ├── __main__.py
│       │       └── server.py
│       ├── test
│       │   └── time_server_test.py
│       └── uv.lock
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/src/filesystem/__tests__/path-validation.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
  2 | import * as path from 'path';
  3 | import * as fs from 'fs/promises';
  4 | import * as os from 'os';
  5 | import { isPathWithinAllowedDirectories } from '../path-validation.js';
  6 | 
  7 | /**
  8 |  * Check if the current environment supports symlink creation
  9 |  */
 10 | async function checkSymlinkSupport(): Promise<boolean> {
 11 |   const testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'symlink-test-'));
 12 |   try {
 13 |     const targetFile = path.join(testDir, 'target.txt');
 14 |     const linkFile = path.join(testDir, 'link.txt');
 15 |     
 16 |     await fs.writeFile(targetFile, 'test');
 17 |     await fs.symlink(targetFile, linkFile);
 18 |     
 19 |     // If we get here, symlinks are supported
 20 |     return true;
 21 |   } catch (error) {
 22 |     // EPERM indicates no symlink permissions
 23 |     if ((error as NodeJS.ErrnoException).code === 'EPERM') {
 24 |       return false;
 25 |     }
 26 |     // Other errors might indicate a real problem
 27 |     throw error;
 28 |   } finally {
 29 |     await fs.rm(testDir, { recursive: true, force: true });
 30 |   }
 31 | }
 32 | 
 33 | // Global variable to store symlink support status
 34 | let symlinkSupported: boolean | null = null;
 35 | 
 36 | /**
 37 |  * Get cached symlink support status, checking once per test run
 38 |  */
 39 | async function getSymlinkSupport(): Promise<boolean> {
 40 |   if (symlinkSupported === null) {
 41 |     symlinkSupported = await checkSymlinkSupport();
 42 |     if (!symlinkSupported) {
 43 |       console.log('\n⚠️  Symlink tests will be skipped - symlink creation not supported in this environment');
 44 |       console.log('   On Windows, enable Developer Mode or run as Administrator to enable symlink tests');
 45 |     }
 46 |   }
 47 |   return symlinkSupported;
 48 | }
 49 | 
 50 | describe('Path Validation', () => {
 51 |   it('allows exact directory match', () => {
 52 |     const allowed = ['/home/user/project'];
 53 |     expect(isPathWithinAllowedDirectories('/home/user/project', allowed)).toBe(true);
 54 |   });
 55 | 
 56 |   it('allows subdirectories', () => {
 57 |     const allowed = ['/home/user/project'];
 58 |     expect(isPathWithinAllowedDirectories('/home/user/project/src', allowed)).toBe(true);
 59 |     expect(isPathWithinAllowedDirectories('/home/user/project/src/index.js', allowed)).toBe(true);
 60 |     expect(isPathWithinAllowedDirectories('/home/user/project/deeply/nested/file.txt', allowed)).toBe(true);
 61 |   });
 62 | 
 63 |   it('blocks similar directory names (prefix vulnerability)', () => {
 64 |     const allowed = ['/home/user/project'];
 65 |     expect(isPathWithinAllowedDirectories('/home/user/project2', allowed)).toBe(false);
 66 |     expect(isPathWithinAllowedDirectories('/home/user/project_backup', allowed)).toBe(false);
 67 |     expect(isPathWithinAllowedDirectories('/home/user/project-old', allowed)).toBe(false);
 68 |     expect(isPathWithinAllowedDirectories('/home/user/projectile', allowed)).toBe(false);
 69 |     expect(isPathWithinAllowedDirectories('/home/user/project.bak', allowed)).toBe(false);
 70 |   });
 71 | 
 72 |   it('blocks paths outside allowed directories', () => {
 73 |     const allowed = ['/home/user/project'];
 74 |     expect(isPathWithinAllowedDirectories('/home/user/other', allowed)).toBe(false);
 75 |     expect(isPathWithinAllowedDirectories('/etc/passwd', allowed)).toBe(false);
 76 |     expect(isPathWithinAllowedDirectories('/home/user', allowed)).toBe(false);
 77 |     expect(isPathWithinAllowedDirectories('/', allowed)).toBe(false);
 78 |   });
 79 | 
 80 |   it('handles multiple allowed directories', () => {
 81 |     const allowed = ['/home/user/project1', '/home/user/project2'];
 82 |     expect(isPathWithinAllowedDirectories('/home/user/project1/src', allowed)).toBe(true);
 83 |     expect(isPathWithinAllowedDirectories('/home/user/project2/src', allowed)).toBe(true);
 84 |     expect(isPathWithinAllowedDirectories('/home/user/project3', allowed)).toBe(false);
 85 |     expect(isPathWithinAllowedDirectories('/home/user/project1_backup', allowed)).toBe(false);
 86 |     expect(isPathWithinAllowedDirectories('/home/user/project2-old', allowed)).toBe(false);
 87 |   });
 88 | 
 89 |   it('blocks parent and sibling directories', () => {
 90 |     const allowed = ['/test/allowed'];
 91 | 
 92 |     // Parent directory
 93 |     expect(isPathWithinAllowedDirectories('/test', allowed)).toBe(false);
 94 |     expect(isPathWithinAllowedDirectories('/', allowed)).toBe(false);
 95 | 
 96 |     // Sibling with common prefix
 97 |     expect(isPathWithinAllowedDirectories('/test/allowed_sibling', allowed)).toBe(false);
 98 |     expect(isPathWithinAllowedDirectories('/test/allowed2', allowed)).toBe(false);
 99 |   });
100 | 
101 |   it('handles paths with special characters', () => {
102 |     const allowed = ['/home/user/my-project (v2)'];
103 | 
104 |     expect(isPathWithinAllowedDirectories('/home/user/my-project (v2)', allowed)).toBe(true);
105 |     expect(isPathWithinAllowedDirectories('/home/user/my-project (v2)/src', allowed)).toBe(true);
106 |     expect(isPathWithinAllowedDirectories('/home/user/my-project (v2)_backup', allowed)).toBe(false);
107 |     expect(isPathWithinAllowedDirectories('/home/user/my-project', allowed)).toBe(false);
108 |   });
109 | 
110 |   describe('Input validation', () => {
111 |     it('rejects empty inputs', () => {
112 |       const allowed = ['/home/user/project'];
113 | 
114 |       expect(isPathWithinAllowedDirectories('', allowed)).toBe(false);
115 |       expect(isPathWithinAllowedDirectories('/home/user/project', [])).toBe(false);
116 |     });
117 | 
118 |     it('handles trailing separators correctly', () => {
119 |       const allowed = ['/home/user/project'];
120 | 
121 |       // Path with trailing separator should still match
122 |       expect(isPathWithinAllowedDirectories('/home/user/project/', allowed)).toBe(true);
123 | 
124 |       // Allowed directory with trailing separator
125 |       const allowedWithSep = ['/home/user/project/'];
126 |       expect(isPathWithinAllowedDirectories('/home/user/project', allowedWithSep)).toBe(true);
127 |       expect(isPathWithinAllowedDirectories('/home/user/project/', allowedWithSep)).toBe(true);
128 | 
129 |       // Should still block similar names with or without trailing separators
130 |       expect(isPathWithinAllowedDirectories('/home/user/project2', allowedWithSep)).toBe(false);
131 |       expect(isPathWithinAllowedDirectories('/home/user/project2', allowed)).toBe(false);
132 |       expect(isPathWithinAllowedDirectories('/home/user/project2/', allowed)).toBe(false);
133 |     });
134 | 
135 |     it('skips empty directory entries in allowed list', () => {
136 |       const allowed = ['', '/home/user/project', ''];
137 |       expect(isPathWithinAllowedDirectories('/home/user/project', allowed)).toBe(true);
138 |       expect(isPathWithinAllowedDirectories('/home/user/project/src', allowed)).toBe(true);
139 | 
140 |       // Should still validate properly with empty entries
141 |       expect(isPathWithinAllowedDirectories('/home/user/other', allowed)).toBe(false);
142 |     });
143 | 
144 |     it('handles Windows paths with trailing separators', () => {
145 |       if (path.sep === '\\') {
146 |         const allowed = ['C:\\Users\\project'];
147 | 
148 |         // Path with trailing separator
149 |         expect(isPathWithinAllowedDirectories('C:\\Users\\project\\', allowed)).toBe(true);
150 | 
151 |         // Allowed with trailing separator
152 |         const allowedWithSep = ['C:\\Users\\project\\'];
153 |         expect(isPathWithinAllowedDirectories('C:\\Users\\project', allowedWithSep)).toBe(true);
154 |         expect(isPathWithinAllowedDirectories('C:\\Users\\project\\', allowedWithSep)).toBe(true);
155 | 
156 |         // Should still block similar names
157 |         expect(isPathWithinAllowedDirectories('C:\\Users\\project2\\', allowed)).toBe(false);
158 |       }
159 |     });
160 |   });
161 | 
162 |   describe('Error handling', () => {
163 |     it('normalizes relative paths to absolute', () => {
164 |       const allowed = [process.cwd()];
165 | 
166 |       // Relative paths get normalized to absolute paths based on cwd
167 |       expect(isPathWithinAllowedDirectories('relative/path', allowed)).toBe(true);
168 |       expect(isPathWithinAllowedDirectories('./file', allowed)).toBe(true);
169 | 
170 |       // Parent directory references that escape allowed directory
171 |       const parentAllowed = ['/home/user/project'];
172 |       expect(isPathWithinAllowedDirectories('../parent', parentAllowed)).toBe(false);
173 |     });
174 | 
175 |     it('returns false for relative paths in allowed directories', () => {
176 |       const badAllowed = ['relative/path', '/some/other/absolute/path'];
177 | 
178 |       // Relative paths in allowed dirs are normalized to absolute based on cwd
179 |       // The normalized 'relative/path' won't match our test path
180 |       expect(isPathWithinAllowedDirectories('/some/other/absolute/path/file', badAllowed)).toBe(true);
181 |       expect(isPathWithinAllowedDirectories('/absolute/path/file', badAllowed)).toBe(false);
182 |     });
183 | 
184 |     it('handles null and undefined inputs gracefully', () => {
185 |       const allowed = ['/home/user/project'];
186 | 
187 |       // Should return false, not crash
188 |       expect(isPathWithinAllowedDirectories(null as any, allowed)).toBe(false);
189 |       expect(isPathWithinAllowedDirectories(undefined as any, allowed)).toBe(false);
190 |       expect(isPathWithinAllowedDirectories('/path', null as any)).toBe(false);
191 |       expect(isPathWithinAllowedDirectories('/path', undefined as any)).toBe(false);
192 |     });
193 |   });
194 | 
195 |   describe('Unicode and special characters', () => {
196 |     it('handles unicode characters in paths', () => {
197 |       const allowed = ['/home/user/café'];
198 | 
199 |       expect(isPathWithinAllowedDirectories('/home/user/café', allowed)).toBe(true);
200 |       expect(isPathWithinAllowedDirectories('/home/user/café/file', allowed)).toBe(true);
201 | 
202 |       // Different unicode representation won't match (not normalized)
203 |       const decomposed = '/home/user/cafe\u0301'; // e + combining accent
204 |       expect(isPathWithinAllowedDirectories(decomposed, allowed)).toBe(false);
205 |     });
206 | 
207 |     it('handles paths with spaces correctly', () => {
208 |       const allowed = ['/home/user/my project'];
209 | 
210 |       expect(isPathWithinAllowedDirectories('/home/user/my project', allowed)).toBe(true);
211 |       expect(isPathWithinAllowedDirectories('/home/user/my project/file', allowed)).toBe(true);
212 | 
213 |       // Partial matches should fail
214 |       expect(isPathWithinAllowedDirectories('/home/user/my', allowed)).toBe(false);
215 |       expect(isPathWithinAllowedDirectories('/home/user/my proj', allowed)).toBe(false);
216 |     });
217 |   });
218 | 
219 |   describe('Overlapping allowed directories', () => {
220 |     it('handles nested allowed directories correctly', () => {
221 |       const allowed = ['/home', '/home/user', '/home/user/project'];
222 | 
223 |       // All paths under /home are allowed
224 |       expect(isPathWithinAllowedDirectories('/home/anything', allowed)).toBe(true);
225 |       expect(isPathWithinAllowedDirectories('/home/user/anything', allowed)).toBe(true);
226 |       expect(isPathWithinAllowedDirectories('/home/user/project/anything', allowed)).toBe(true);
227 | 
228 |       // First match wins (most permissive)
229 |       expect(isPathWithinAllowedDirectories('/home/other/deep/path', allowed)).toBe(true);
230 |     });
231 | 
232 |     it('handles root directory as allowed', () => {
233 |       const allowed = ['/'];
234 | 
235 |       // Everything is allowed under root (dangerous configuration)
236 |       expect(isPathWithinAllowedDirectories('/', allowed)).toBe(true);
237 |       expect(isPathWithinAllowedDirectories('/any/path', allowed)).toBe(true);
238 |       expect(isPathWithinAllowedDirectories('/etc/passwd', allowed)).toBe(true);
239 |       expect(isPathWithinAllowedDirectories('/home/user/secret', allowed)).toBe(true);
240 | 
241 |       // But only on the same filesystem root
242 |       if (path.sep === '\\') {
243 |         expect(isPathWithinAllowedDirectories('D:\\other', ['/'])).toBe(false);
244 |       }
245 |     });
246 |   });
247 | 
248 |   describe('Cross-platform behavior', () => {
249 |     it('handles Windows-style paths on Windows', () => {
250 |       if (path.sep === '\\') {
251 |         const allowed = ['C:\\Users\\project'];
252 |         expect(isPathWithinAllowedDirectories('C:\\Users\\project', allowed)).toBe(true);
253 |         expect(isPathWithinAllowedDirectories('C:\\Users\\project\\src', allowed)).toBe(true);
254 |         expect(isPathWithinAllowedDirectories('C:\\Users\\project2', allowed)).toBe(false);
255 |         expect(isPathWithinAllowedDirectories('C:\\Users\\project_backup', allowed)).toBe(false);
256 |       }
257 |     });
258 | 
259 |     it('handles Unix-style paths on Unix', () => {
260 |       if (path.sep === '/') {
261 |         const allowed = ['/home/user/project'];
262 |         expect(isPathWithinAllowedDirectories('/home/user/project', allowed)).toBe(true);
263 |         expect(isPathWithinAllowedDirectories('/home/user/project/src', allowed)).toBe(true);
264 |         expect(isPathWithinAllowedDirectories('/home/user/project2', allowed)).toBe(false);
265 |       }
266 |     });
267 |   });
268 | 
269 |   describe('Validation Tests - Path Traversal', () => {
270 |     it('blocks path traversal attempts', () => {
271 |       const allowed = ['/home/user/project'];
272 | 
273 |       // Basic traversal attempts
274 |       expect(isPathWithinAllowedDirectories('/home/user/project/../../../etc/passwd', allowed)).toBe(false);
275 |       expect(isPathWithinAllowedDirectories('/home/user/project/../../other', allowed)).toBe(false);
276 |       expect(isPathWithinAllowedDirectories('/home/user/project/../project2', allowed)).toBe(false);
277 | 
278 |       // Mixed traversal with valid segments
279 |       expect(isPathWithinAllowedDirectories('/home/user/project/src/../../project2', allowed)).toBe(false);
280 |       expect(isPathWithinAllowedDirectories('/home/user/project/./../../other', allowed)).toBe(false);
281 | 
282 |       // Multiple traversal sequences
283 |       expect(isPathWithinAllowedDirectories('/home/user/project/../project/../../../etc', allowed)).toBe(false);
284 |     });
285 | 
286 |     it('blocks traversal in allowed directories', () => {
287 |       const allowed = ['/home/user/project/../safe'];
288 | 
289 |       // The allowed directory itself should be normalized and safe
290 |       expect(isPathWithinAllowedDirectories('/home/user/safe/file', allowed)).toBe(true);
291 |       expect(isPathWithinAllowedDirectories('/home/user/project/file', allowed)).toBe(false);
292 |     });
293 | 
294 |     it('handles complex traversal patterns', () => {
295 |       const allowed = ['/home/user/project'];
296 | 
297 |       // Double dots in filenames (not traversal) - these normalize to paths within allowed dir
298 |       expect(isPathWithinAllowedDirectories('/home/user/project/..test', allowed)).toBe(true); // Not traversal
299 |       expect(isPathWithinAllowedDirectories('/home/user/project/test..', allowed)).toBe(true); // Not traversal
300 |       expect(isPathWithinAllowedDirectories('/home/user/project/te..st', allowed)).toBe(true); // Not traversal
301 | 
302 |       // Actual traversal
303 |       expect(isPathWithinAllowedDirectories('/home/user/project/../test', allowed)).toBe(false); // Is traversal - goes to /home/user/test
304 | 
305 |       // Edge case: /home/user/project/.. normalizes to /home/user (parent dir)
306 |       expect(isPathWithinAllowedDirectories('/home/user/project/..', allowed)).toBe(false); // Goes to parent
307 |     });
308 |   });
309 | 
310 |   describe('Validation Tests - Null Bytes', () => {
311 |     it('rejects paths with null bytes', () => {
312 |       const allowed = ['/home/user/project'];
313 | 
314 |       expect(isPathWithinAllowedDirectories('/home/user/project\x00/etc/passwd', allowed)).toBe(false);
315 |       expect(isPathWithinAllowedDirectories('/home/user/project/test\x00.txt', allowed)).toBe(false);
316 |       expect(isPathWithinAllowedDirectories('\x00/home/user/project', allowed)).toBe(false);
317 |       expect(isPathWithinAllowedDirectories('/home/user/project/\x00', allowed)).toBe(false);
318 |     });
319 | 
320 |     it('rejects allowed directories with null bytes', () => {
321 |       const allowed = ['/home/user/project\x00'];
322 | 
323 |       expect(isPathWithinAllowedDirectories('/home/user/project', allowed)).toBe(false);
324 |       expect(isPathWithinAllowedDirectories('/home/user/project/file', allowed)).toBe(false);
325 |     });
326 |   });
327 | 
328 |   describe('Validation Tests - Special Characters', () => {
329 |     it('allows percent signs in filenames', () => {
330 |       const allowed = ['/home/user/project'];
331 | 
332 |       // Percent is a valid filename character
333 |       expect(isPathWithinAllowedDirectories('/home/user/project/report_50%.pdf', allowed)).toBe(true);
334 |       expect(isPathWithinAllowedDirectories('/home/user/project/Q1_25%_growth', allowed)).toBe(true);
335 |       expect(isPathWithinAllowedDirectories('/home/user/project/%41', allowed)).toBe(true); // File named %41
336 | 
337 |       // URL encoding is NOT decoded by path.normalize, so these are just odd filenames
338 |       expect(isPathWithinAllowedDirectories('/home/user/project/%2e%2e', allowed)).toBe(true); // File named "%2e%2e"
339 |       expect(isPathWithinAllowedDirectories('/home/user/project/file%20name', allowed)).toBe(true); // File with %20 in name
340 |     });
341 | 
342 |     it('handles percent signs in allowed directories', () => {
343 |       const allowed = ['/home/user/project%20files'];
344 | 
345 |       // This is a directory literally named "project%20files"
346 |       expect(isPathWithinAllowedDirectories('/home/user/project%20files/test', allowed)).toBe(true);
347 |       expect(isPathWithinAllowedDirectories('/home/user/project files/test', allowed)).toBe(false); // Different dir
348 |     });
349 |   });
350 | 
351 |   describe('Path Normalization', () => {
352 |     it('normalizes paths before comparison', () => {
353 |       const allowed = ['/home/user/project'];
354 | 
355 |       // Trailing slashes
356 |       expect(isPathWithinAllowedDirectories('/home/user/project/', allowed)).toBe(true);
357 |       expect(isPathWithinAllowedDirectories('/home/user/project//', allowed)).toBe(true);
358 |       expect(isPathWithinAllowedDirectories('/home/user/project///', allowed)).toBe(true);
359 | 
360 |       // Current directory references
361 |       expect(isPathWithinAllowedDirectories('/home/user/project/./src', allowed)).toBe(true);
362 |       expect(isPathWithinAllowedDirectories('/home/user/./project/src', allowed)).toBe(true);
363 | 
364 |       // Multiple slashes
365 |       expect(isPathWithinAllowedDirectories('/home/user/project//src//file', allowed)).toBe(true);
366 |       expect(isPathWithinAllowedDirectories('/home//user//project//src', allowed)).toBe(true);
367 | 
368 |       // Should still block outside paths
369 |       expect(isPathWithinAllowedDirectories('/home/user//project2', allowed)).toBe(false);
370 |     });
371 | 
372 |     it('handles mixed separators correctly', () => {
373 |       if (path.sep === '\\') {
374 |         const allowed = ['C:\\Users\\project'];
375 | 
376 |         // Mixed separators should be normalized
377 |         expect(isPathWithinAllowedDirectories('C:/Users/project', allowed)).toBe(true);
378 |         expect(isPathWithinAllowedDirectories('C:\\Users/project\\src', allowed)).toBe(true);
379 |         expect(isPathWithinAllowedDirectories('C:/Users\\project/src', allowed)).toBe(true);
380 |       }
381 |     });
382 |   });
383 | 
384 |   describe('Edge Cases', () => {
385 |     it('rejects non-string inputs safely', () => {
386 |       const allowed = ['/home/user/project'];
387 | 
388 |       expect(isPathWithinAllowedDirectories(123 as any, allowed)).toBe(false);
389 |       expect(isPathWithinAllowedDirectories({} as any, allowed)).toBe(false);
390 |       expect(isPathWithinAllowedDirectories([] as any, allowed)).toBe(false);
391 |       expect(isPathWithinAllowedDirectories(null as any, allowed)).toBe(false);
392 |       expect(isPathWithinAllowedDirectories(undefined as any, allowed)).toBe(false);
393 | 
394 |       // Non-string in allowed directories
395 |       expect(isPathWithinAllowedDirectories('/home/user/project', [123 as any])).toBe(false);
396 |       expect(isPathWithinAllowedDirectories('/home/user/project', [{} as any])).toBe(false);
397 |     });
398 | 
399 |     it('handles very long paths', () => {
400 |       const allowed = ['/home/user/project'];
401 | 
402 |       // Create a very long path that's still valid
403 |       const longSubPath = 'a/'.repeat(1000) + 'file.txt';
404 |       expect(isPathWithinAllowedDirectories(`/home/user/project/${longSubPath}`, allowed)).toBe(true);
405 | 
406 |       // Very long path that escapes
407 |       const escapePath = 'a/'.repeat(1000) + '../'.repeat(1001) + 'etc/passwd';
408 |       expect(isPathWithinAllowedDirectories(`/home/user/project/${escapePath}`, allowed)).toBe(false);
409 |     });
410 |   });
411 | 
412 |   describe('Additional Coverage', () => {
413 |     it('handles allowed directories with traversal that normalizes safely', () => {
414 |       // These allowed dirs contain traversal but normalize to valid paths
415 |       const allowed = ['/home/user/../user/project'];
416 | 
417 |       // Should normalize to /home/user/project and work correctly
418 |       expect(isPathWithinAllowedDirectories('/home/user/project/file', allowed)).toBe(true);
419 |       expect(isPathWithinAllowedDirectories('/home/user/other', allowed)).toBe(false);
420 |     });
421 | 
422 |     it('handles symbolic dots in filenames', () => {
423 |       const allowed = ['/home/user/project'];
424 | 
425 |       // Single and double dots as actual filenames (not traversal)
426 |       expect(isPathWithinAllowedDirectories('/home/user/project/.', allowed)).toBe(true);
427 |       expect(isPathWithinAllowedDirectories('/home/user/project/..', allowed)).toBe(false); // This normalizes to parent
428 |       expect(isPathWithinAllowedDirectories('/home/user/project/...', allowed)).toBe(true); // Three dots is a valid filename
429 |       expect(isPathWithinAllowedDirectories('/home/user/project/....', allowed)).toBe(true); // Four dots is a valid filename
430 |     });
431 | 
432 |     it('handles UNC paths on Windows', () => {
433 |       if (path.sep === '\\') {
434 |         const allowed = ['\\\\server\\share\\project'];
435 | 
436 |         expect(isPathWithinAllowedDirectories('\\\\server\\share\\project', allowed)).toBe(true);
437 |         expect(isPathWithinAllowedDirectories('\\\\server\\share\\project\\file', allowed)).toBe(true);
438 |         expect(isPathWithinAllowedDirectories('\\\\server\\share\\other', allowed)).toBe(false);
439 |         expect(isPathWithinAllowedDirectories('\\\\other\\share\\project', allowed)).toBe(false);
440 |       }
441 |     });
442 |   });
443 | 
444 |   describe('Symlink Tests', () => {
445 |     let testDir: string;
446 |     let allowedDir: string;
447 |     let forbiddenDir: string;
448 | 
449 |     beforeEach(async () => {
450 |       testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fs-error-test-'));
451 |       allowedDir = path.join(testDir, 'allowed');
452 |       forbiddenDir = path.join(testDir, 'forbidden');
453 | 
454 |       await fs.mkdir(allowedDir, { recursive: true });
455 |       await fs.mkdir(forbiddenDir, { recursive: true });
456 |     });
457 | 
458 |     afterEach(async () => {
459 |       await fs.rm(testDir, { recursive: true, force: true });
460 |     });
461 | 
462 |     it('validates symlink handling', async () => {
463 |       // Test with symlinks
464 |       try {
465 |         const linkPath = path.join(allowedDir, 'bad-link');
466 |         const targetPath = path.join(forbiddenDir, 'target.txt');
467 | 
468 |         await fs.writeFile(targetPath, 'content');
469 |         await fs.symlink(targetPath, linkPath);
470 | 
471 |         // In real implementation, this would throw with the resolved path
472 |         const realPath = await fs.realpath(linkPath);
473 |         const allowed = [allowedDir];
474 | 
475 |         // Symlink target should be outside allowed directory
476 |         expect(isPathWithinAllowedDirectories(realPath, allowed)).toBe(false);
477 |       } catch (error) {
478 |         // Skip if no symlink permissions
479 |       }
480 |     });
481 | 
482 |     it('handles non-existent paths correctly', async () => {
483 |       const newFilePath = path.join(allowedDir, 'subdir', 'newfile.txt');
484 | 
485 |       // Parent directory doesn't exist
486 |       try {
487 |         await fs.access(newFilePath);
488 |       } catch (error) {
489 |         expect((error as NodeJS.ErrnoException).code).toBe('ENOENT');
490 |       }
491 | 
492 |       // After creating parent, validation should work
493 |       await fs.mkdir(path.dirname(newFilePath), { recursive: true });
494 |       const allowed = [allowedDir];
495 |       expect(isPathWithinAllowedDirectories(newFilePath, allowed)).toBe(true);
496 |     });
497 | 
498 |     // Test path resolution consistency for symlinked files
499 |     it('validates symlinked files consistently between path and resolved forms', async () => {
500 |       try {
501 |         // Setup: Create target file in forbidden area
502 |         const targetFile = path.join(forbiddenDir, 'target.txt');
503 |         await fs.writeFile(targetFile, 'TARGET_CONTENT');
504 | 
505 |         // Create symlink inside allowed directory pointing to forbidden file
506 |         const symlinkPath = path.join(allowedDir, 'link-to-target.txt');
507 |         await fs.symlink(targetFile, symlinkPath);
508 | 
509 |         // The symlink path itself passes validation (looks like it's in allowed dir)
510 |         expect(isPathWithinAllowedDirectories(symlinkPath, [allowedDir])).toBe(true);
511 | 
512 |         // But the resolved path should fail validation
513 |         const resolvedPath = await fs.realpath(symlinkPath);
514 |         expect(isPathWithinAllowedDirectories(resolvedPath, [allowedDir])).toBe(false);
515 | 
516 |         // Verify the resolved path goes to the forbidden location (normalize both paths for macOS temp dirs)
517 |         expect(await fs.realpath(resolvedPath)).toBe(await fs.realpath(targetFile));
518 |       } catch (error) {
519 |         // Skip if no symlink permissions on the system
520 |         if ((error as NodeJS.ErrnoException).code !== 'EPERM') {
521 |           throw error;
522 |         }
523 |       }
524 |     });
525 | 
526 |     // Test allowed directory resolution behavior
527 |     it('validates paths correctly when allowed directory is resolved from symlink', async () => {
528 |       try {
529 |         // Setup: Create the actual target directory with content
530 |         const actualTargetDir = path.join(testDir, 'actual-target');
531 |         await fs.mkdir(actualTargetDir, { recursive: true });
532 |         const targetFile = path.join(actualTargetDir, 'file.txt');
533 |         await fs.writeFile(targetFile, 'FILE_CONTENT');
534 | 
535 |         // Setup: Create symlink directory that points to target
536 |         const symlinkDir = path.join(testDir, 'symlink-dir');
537 |         await fs.symlink(actualTargetDir, symlinkDir);
538 | 
539 |         // Simulate resolved allowed directory (what the server startup should do)
540 |         const resolvedAllowedDir = await fs.realpath(symlinkDir);
541 |         const resolvedTargetDir = await fs.realpath(actualTargetDir);
542 |         expect(resolvedAllowedDir).toBe(resolvedTargetDir);
543 | 
544 |         // Test 1: File access through original symlink path should pass validation with resolved allowed dir
545 |         const fileViaSymlink = path.join(symlinkDir, 'file.txt');
546 |         const resolvedFile = await fs.realpath(fileViaSymlink);
547 |         expect(isPathWithinAllowedDirectories(resolvedFile, [resolvedAllowedDir])).toBe(true);
548 | 
549 |         // Test 2: File access through resolved path should also pass validation
550 |         const fileViaResolved = path.join(resolvedTargetDir, 'file.txt');
551 |         expect(isPathWithinAllowedDirectories(fileViaResolved, [resolvedAllowedDir])).toBe(true);
552 | 
553 |         // Test 3: Demonstrate inconsistent behavior with unresolved allowed directories
554 |         // If allowed dirs were not resolved (storing symlink paths instead):
555 |         const unresolvedAllowedDirs = [symlinkDir];
556 |         // This validation would incorrectly fail for the same content:
557 |         expect(isPathWithinAllowedDirectories(resolvedFile, unresolvedAllowedDirs)).toBe(false);
558 | 
559 |       } catch (error) {
560 |         // Skip if no symlink permissions on the system
561 |         if ((error as NodeJS.ErrnoException).code !== 'EPERM') {
562 |           throw error;
563 |         }
564 |       }
565 |     });
566 | 
567 |     it('resolves nested symlink chains completely', async () => {
568 |       try {
569 |         // Setup: Create target file in forbidden area
570 |         const actualTarget = path.join(forbiddenDir, 'target-file.txt');
571 |         await fs.writeFile(actualTarget, 'FINAL_CONTENT');
572 | 
573 |         // Create chain of symlinks: allowedFile -> link2 -> link1 -> actualTarget
574 |         const link1 = path.join(testDir, 'intermediate-link1');
575 |         const link2 = path.join(testDir, 'intermediate-link2');
576 |         const allowedFile = path.join(allowedDir, 'seemingly-safe-file');
577 | 
578 |         await fs.symlink(actualTarget, link1);
579 |         await fs.symlink(link1, link2);
580 |         await fs.symlink(link2, allowedFile);
581 | 
582 |         // The allowed file path passes basic validation
583 |         expect(isPathWithinAllowedDirectories(allowedFile, [allowedDir])).toBe(true);
584 | 
585 |         // But complete resolution reveals the forbidden target
586 |         const fullyResolvedPath = await fs.realpath(allowedFile);
587 |         expect(isPathWithinAllowedDirectories(fullyResolvedPath, [allowedDir])).toBe(false);
588 |         expect(await fs.realpath(fullyResolvedPath)).toBe(await fs.realpath(actualTarget));
589 | 
590 |       } catch (error) {
591 |         // Skip if no symlink permissions on the system
592 |         if ((error as NodeJS.ErrnoException).code !== 'EPERM') {
593 |           throw error;
594 |         }
595 |       }
596 |     });
597 |   });
598 | 
599 |   describe('Path Validation Race Condition Tests', () => {
600 |     let testDir: string;
601 |     let allowedDir: string;
602 |     let forbiddenDir: string;
603 |     let targetFile: string;
604 |     let testPath: string;
605 | 
606 |     beforeEach(async () => {
607 |       testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'race-test-'));
608 |       allowedDir = path.join(testDir, 'allowed');
609 |       forbiddenDir = path.join(testDir, 'outside');
610 |       targetFile = path.join(forbiddenDir, 'target.txt');
611 |       testPath = path.join(allowedDir, 'test.txt');
612 | 
613 |       await fs.mkdir(allowedDir, { recursive: true });
614 |       await fs.mkdir(forbiddenDir, { recursive: true });
615 |       await fs.writeFile(targetFile, 'ORIGINAL CONTENT', 'utf-8');
616 |     });
617 | 
618 |     afterEach(async () => {
619 |       await fs.rm(testDir, { recursive: true, force: true });
620 |     });
621 | 
622 |     it('validates non-existent file paths based on parent directory', async () => {
623 |       const allowed = [allowedDir];
624 | 
625 |       expect(isPathWithinAllowedDirectories(testPath, allowed)).toBe(true);
626 |       await expect(fs.access(testPath)).rejects.toThrow();
627 | 
628 |       const parentDir = path.dirname(testPath);
629 |       expect(isPathWithinAllowedDirectories(parentDir, allowed)).toBe(true);
630 |     });
631 | 
632 |     it('demonstrates symlink race condition allows writing outside allowed directories', async () => {
633 |       const symlinkSupported = await getSymlinkSupport();
634 |       if (!symlinkSupported) {
635 |         console.log('   ⏭️  Skipping symlink race condition test - symlinks not supported');
636 |         return;
637 |       }
638 | 
639 |       const allowed = [allowedDir];
640 | 
641 |       await expect(fs.access(testPath)).rejects.toThrow();
642 |       expect(isPathWithinAllowedDirectories(testPath, allowed)).toBe(true);
643 | 
644 |       await fs.symlink(targetFile, testPath);
645 |       await fs.writeFile(testPath, 'MODIFIED CONTENT', 'utf-8');
646 | 
647 |       const targetContent = await fs.readFile(targetFile, 'utf-8');
648 |       expect(targetContent).toBe('MODIFIED CONTENT');
649 | 
650 |       const resolvedPath = await fs.realpath(testPath);
651 |       expect(isPathWithinAllowedDirectories(resolvedPath, allowed)).toBe(false);
652 |     });
653 | 
654 |     it('shows timing differences between validation approaches', async () => {
655 |       const symlinkSupported = await getSymlinkSupport();
656 |       if (!symlinkSupported) {
657 |         console.log('   ⏭️  Skipping timing validation test - symlinks not supported');
658 |         return;
659 |       }
660 | 
661 |       const allowed = [allowedDir];
662 | 
663 |       const validation1 = isPathWithinAllowedDirectories(testPath, allowed);
664 |       expect(validation1).toBe(true);
665 | 
666 |       await fs.symlink(targetFile, testPath);
667 | 
668 |       const resolvedPath = await fs.realpath(testPath);
669 |       const validation2 = isPathWithinAllowedDirectories(resolvedPath, allowed);
670 |       expect(validation2).toBe(false);
671 | 
672 |       expect(validation1).not.toBe(validation2);
673 |     });
674 | 
675 |     it('validates directory creation timing', async () => {
676 |       const symlinkSupported = await getSymlinkSupport();
677 |       if (!symlinkSupported) {
678 |         console.log('   ⏭️  Skipping directory creation timing test - symlinks not supported');
679 |         return;
680 |       }
681 | 
682 |       const allowed = [allowedDir];
683 |       const testDir = path.join(allowedDir, 'newdir');
684 | 
685 |       expect(isPathWithinAllowedDirectories(testDir, allowed)).toBe(true);
686 | 
687 |       await fs.symlink(forbiddenDir, testDir);
688 | 
689 |       expect(isPathWithinAllowedDirectories(testDir, allowed)).toBe(true);
690 | 
691 |       const resolved = await fs.realpath(testDir);
692 |       expect(isPathWithinAllowedDirectories(resolved, allowed)).toBe(false);
693 |     });
694 | 
695 |     it('demonstrates exclusive file creation behavior', async () => {
696 |       const symlinkSupported = await getSymlinkSupport();
697 |       if (!symlinkSupported) {
698 |         console.log('   ⏭️  Skipping exclusive file creation test - symlinks not supported');
699 |         return;
700 |       }
701 | 
702 |       const allowed = [allowedDir];
703 | 
704 |       await fs.symlink(targetFile, testPath);
705 | 
706 |       await expect(fs.open(testPath, 'wx')).rejects.toThrow(/EEXIST/);
707 | 
708 |       await fs.writeFile(testPath, 'NEW CONTENT', 'utf-8');
709 |       const targetContent = await fs.readFile(targetFile, 'utf-8');
710 |       expect(targetContent).toBe('NEW CONTENT');
711 |     });
712 | 
713 |     it('should use resolved parent paths for non-existent files', async () => {
714 |       const symlinkSupported = await getSymlinkSupport();
715 |       if (!symlinkSupported) {
716 |         console.log('   ⏭️  Skipping resolved parent paths test - symlinks not supported');
717 |         return;
718 |       }
719 | 
720 |       const allowed = [allowedDir];
721 | 
722 |       const symlinkDir = path.join(allowedDir, 'link');
723 |       await fs.symlink(forbiddenDir, symlinkDir);
724 | 
725 |       const fileThroughSymlink = path.join(symlinkDir, 'newfile.txt');
726 | 
727 |       expect(fileThroughSymlink.startsWith(allowedDir)).toBe(true);
728 | 
729 |       const parentDir = path.dirname(fileThroughSymlink);
730 |       const resolvedParent = await fs.realpath(parentDir);
731 |       expect(isPathWithinAllowedDirectories(resolvedParent, allowed)).toBe(false);
732 | 
733 |       const expectedSafePath = path.join(resolvedParent, path.basename(fileThroughSymlink));
734 |       expect(isPathWithinAllowedDirectories(expectedSafePath, allowed)).toBe(false);
735 |     });
736 | 
737 |     it('demonstrates parent directory symlink traversal', async () => {
738 |       const symlinkSupported = await getSymlinkSupport();
739 |       if (!symlinkSupported) {
740 |         console.log('   ⏭️  Skipping parent directory symlink traversal test - symlinks not supported');
741 |         return;
742 |       }
743 | 
744 |       const allowed = [allowedDir];
745 |       const deepPath = path.join(allowedDir, 'sub1', 'sub2', 'file.txt');
746 | 
747 |       expect(isPathWithinAllowedDirectories(deepPath, allowed)).toBe(true);
748 | 
749 |       const sub1Path = path.join(allowedDir, 'sub1');
750 |       await fs.symlink(forbiddenDir, sub1Path);
751 | 
752 |       await fs.mkdir(path.join(sub1Path, 'sub2'), { recursive: true });
753 |       await fs.writeFile(deepPath, 'CONTENT', 'utf-8');
754 | 
755 |       const realPath = await fs.realpath(deepPath);
756 |       const realAllowedDir = await fs.realpath(allowedDir);
757 |       const realForbiddenDir = await fs.realpath(forbiddenDir);
758 | 
759 |       expect(realPath.startsWith(realAllowedDir)).toBe(false);
760 |       expect(realPath.startsWith(realForbiddenDir)).toBe(true);
761 |     });
762 | 
763 |     it('should prevent race condition between validatePath and file operation', async () => {
764 |       const symlinkSupported = await getSymlinkSupport();
765 |       if (!symlinkSupported) {
766 |         console.log('   ⏭️  Skipping race condition prevention test - symlinks not supported');
767 |         return;
768 |       }
769 | 
770 |       const allowed = [allowedDir];
771 |       const racePath = path.join(allowedDir, 'race-file.txt');
772 |       const targetFile = path.join(forbiddenDir, 'target.txt');
773 | 
774 |       await fs.writeFile(targetFile, 'ORIGINAL CONTENT', 'utf-8');
775 | 
776 |       // Path validation would pass (file doesn't exist, parent is in allowed dir)
777 |       expect(await fs.access(racePath).then(() => false).catch(() => true)).toBe(true);
778 |       expect(isPathWithinAllowedDirectories(racePath, allowed)).toBe(true);
779 | 
780 |       // Race condition: symlink created after validation but before write
781 |       await fs.symlink(targetFile, racePath);
782 | 
783 |       // With exclusive write flag, write should fail on symlink
784 |       await expect(
785 |         fs.writeFile(racePath, 'NEW CONTENT', { encoding: 'utf-8', flag: 'wx' })
786 |       ).rejects.toThrow(/EEXIST/);
787 | 
788 |       // Verify content unchanged
789 |       const targetContent = await fs.readFile(targetFile, 'utf-8');
790 |       expect(targetContent).toBe('ORIGINAL CONTENT');
791 | 
792 |       // The symlink exists but write was blocked
793 |       const actualWritePath = await fs.realpath(racePath);
794 |       expect(actualWritePath).toBe(await fs.realpath(targetFile));
795 |       expect(isPathWithinAllowedDirectories(actualWritePath, allowed)).toBe(false);
796 |     });
797 | 
798 |     it('should allow overwrites to legitimate files within allowed directories', async () => {
799 |       const allowed = [allowedDir];
800 |       const legitFile = path.join(allowedDir, 'legit-file.txt');
801 | 
802 |       // Create a legitimate file
803 |       await fs.writeFile(legitFile, 'ORIGINAL', 'utf-8');
804 | 
805 |       // Opening with w should work for legitimate files
806 |       const fd = await fs.open(legitFile, 'w');
807 |       try {
808 |         await fd.write('UPDATED', 0, 'utf-8');
809 |       } finally {
810 |         await fd.close();
811 |       }
812 | 
813 |       const content = await fs.readFile(legitFile, 'utf-8');
814 |       expect(content).toBe('UPDATED');
815 |     });
816 | 
817 |     it('should handle symlinks that point within allowed directories', async () => {
818 |       const symlinkSupported = await getSymlinkSupport();
819 |       if (!symlinkSupported) {
820 |         console.log('   ⏭️  Skipping symlinks within allowed directories test - symlinks not supported');
821 |         return;
822 |       }
823 | 
824 |       const allowed = [allowedDir];
825 |       const targetFile = path.join(allowedDir, 'target.txt');
826 |       const symlinkPath = path.join(allowedDir, 'symlink.txt');
827 | 
828 |       // Create target file within allowed directory
829 |       await fs.writeFile(targetFile, 'TARGET CONTENT', 'utf-8');
830 | 
831 |       // Create symlink pointing to allowed file
832 |       await fs.symlink(targetFile, symlinkPath);
833 | 
834 |       // Opening symlink with w follows it to the target
835 |       const fd = await fs.open(symlinkPath, 'w');
836 |       try {
837 |         await fd.write('UPDATED VIA SYMLINK', 0, 'utf-8');
838 |       } finally {
839 |         await fd.close();
840 |       }
841 | 
842 |       // Both symlink and target should show updated content
843 |       const symlinkContent = await fs.readFile(symlinkPath, 'utf-8');
844 |       const targetContent = await fs.readFile(targetFile, 'utf-8');
845 |       expect(symlinkContent).toBe('UPDATED VIA SYMLINK');
846 |       expect(targetContent).toBe('UPDATED VIA SYMLINK');
847 |     });
848 | 
849 |     it('should prevent overwriting files through symlinks pointing outside allowed directories', async () => {
850 |       const symlinkSupported = await getSymlinkSupport();
851 |       if (!symlinkSupported) {
852 |         console.log('   ⏭️  Skipping symlink overwrite prevention test - symlinks not supported');
853 |         return;
854 |       }
855 | 
856 |       const allowed = [allowedDir];
857 |       const legitFile = path.join(allowedDir, 'existing.txt');
858 |       const targetFile = path.join(forbiddenDir, 'target.txt');
859 | 
860 |       // Create a legitimate file first
861 |       await fs.writeFile(legitFile, 'LEGIT CONTENT', 'utf-8');
862 | 
863 |       // Create target file in forbidden directory
864 |       await fs.writeFile(targetFile, 'FORBIDDEN CONTENT', 'utf-8');
865 | 
866 |       // Now replace the legitimate file with a symlink to forbidden location
867 |       await fs.unlink(legitFile);
868 |       await fs.symlink(targetFile, legitFile);
869 | 
870 |       // Simulate the server's validation logic
871 |       const stats = await fs.lstat(legitFile);
872 |       expect(stats.isSymbolicLink()).toBe(true);
873 | 
874 |       const realPath = await fs.realpath(legitFile);
875 |       expect(isPathWithinAllowedDirectories(realPath, allowed)).toBe(false);
876 | 
877 |       // With atomic rename, symlinks are replaced not followed
878 |       // So this test now demonstrates the protection
879 | 
880 |       // Verify content remains unchanged
881 |       const targetContent = await fs.readFile(targetFile, 'utf-8');
882 |       expect(targetContent).toBe('FORBIDDEN CONTENT');
883 |     });
884 | 
885 |     it('demonstrates race condition in read operations', async () => {
886 |       const symlinkSupported = await getSymlinkSupport();
887 |       if (!symlinkSupported) {
888 |         console.log('   ⏭️  Skipping race condition in read operations test - symlinks not supported');
889 |         return;
890 |       }
891 | 
892 |       const allowed = [allowedDir];
893 |       const legitFile = path.join(allowedDir, 'readable.txt');
894 |       const secretFile = path.join(forbiddenDir, 'secret.txt');
895 | 
896 |       // Create legitimate file
897 |       await fs.writeFile(legitFile, 'PUBLIC CONTENT', 'utf-8');
898 | 
899 |       // Create secret file in forbidden directory
900 |       await fs.writeFile(secretFile, 'SECRET CONTENT', 'utf-8');
901 | 
902 |       // Step 1: validatePath would pass for legitimate file
903 |       expect(isPathWithinAllowedDirectories(legitFile, allowed)).toBe(true);
904 | 
905 |       // Step 2: Race condition - replace file with symlink after validation
906 |       await fs.unlink(legitFile);
907 |       await fs.symlink(secretFile, legitFile);
908 | 
909 |       // Step 3: Read operation follows symlink to forbidden location
910 |       const content = await fs.readFile(legitFile, 'utf-8');
911 | 
912 |       // This shows the vulnerability - we read forbidden content
913 |       expect(content).toBe('SECRET CONTENT');
914 |       expect(isPathWithinAllowedDirectories(await fs.realpath(legitFile), allowed)).toBe(false);
915 |     });
916 | 
917 |     it('verifies rename does not follow symlinks', async () => {
918 |       const symlinkSupported = await getSymlinkSupport();
919 |       if (!symlinkSupported) {
920 |         console.log('   ⏭️  Skipping rename symlink test - symlinks not supported');
921 |         return;
922 |       }
923 | 
924 |       const allowed = [allowedDir];
925 |       const tempFile = path.join(allowedDir, 'temp.txt');
926 |       const targetSymlink = path.join(allowedDir, 'target-symlink.txt');
927 |       const forbiddenTarget = path.join(forbiddenDir, 'forbidden-target.txt');
928 | 
929 |       // Create forbidden target
930 |       await fs.writeFile(forbiddenTarget, 'ORIGINAL CONTENT', 'utf-8');
931 | 
932 |       // Create symlink pointing to forbidden location
933 |       await fs.symlink(forbiddenTarget, targetSymlink);
934 | 
935 |       // Write temp file
936 |       await fs.writeFile(tempFile, 'NEW CONTENT', 'utf-8');
937 | 
938 |       // Rename temp file to symlink path
939 |       await fs.rename(tempFile, targetSymlink);
940 | 
941 |       // Check what happened
942 |       const symlinkExists = await fs.lstat(targetSymlink).then(() => true).catch(() => false);
943 |       const isSymlink = symlinkExists && (await fs.lstat(targetSymlink)).isSymbolicLink();
944 |       const targetContent = await fs.readFile(targetSymlink, 'utf-8');
945 |       const forbiddenContent = await fs.readFile(forbiddenTarget, 'utf-8');
946 | 
947 |       // Rename should replace the symlink with a regular file
948 |       expect(isSymlink).toBe(false);
949 |       expect(targetContent).toBe('NEW CONTENT');
950 |       expect(forbiddenContent).toBe('ORIGINAL CONTENT'); // Unchanged
951 |     });
952 |   });
953 | });
954 | 
```
Page 5/5FirstPrevNextLast