| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- import path from 'pathe';
- import {
- assertFileNameSafeForBaseDir,
- isFileNameSafeForBaseDir,
- isPathWithinBase,
- } from './safe-path-utils';
- describe('path-utils', () => {
- describe('isPathWithinBase', () => {
- const baseDir = '/tmp/growi-export';
- describe('valid paths', () => {
- test('should return true for file directly in baseDir', () => {
- const filePath = '/tmp/growi-export/test.json';
- expect(isPathWithinBase(filePath, baseDir)).toBe(true);
- });
- test('should return true for file in subdirectory', () => {
- const filePath = '/tmp/growi-export/subdir/test.json';
- expect(isPathWithinBase(filePath, baseDir)).toBe(true);
- });
- test('should return true for baseDir itself', () => {
- expect(isPathWithinBase(baseDir, baseDir)).toBe(true);
- });
- test('should handle relative paths correctly', () => {
- const filePath = path.join(baseDir, 'test.json');
- expect(isPathWithinBase(filePath, baseDir)).toBe(true);
- });
- });
- describe('invalid paths (path traversal attacks)', () => {
- test('should return false for path outside baseDir', () => {
- const filePath = '/etc/passwd';
- expect(isPathWithinBase(filePath, baseDir)).toBe(false);
- });
- test('should return false for path traversal with ../', () => {
- const filePath = '/tmp/growi-export/../etc/passwd';
- expect(isPathWithinBase(filePath, baseDir)).toBe(false);
- });
- test('should return false for sibling directory', () => {
- const filePath = '/tmp/other-dir/test.json';
- expect(isPathWithinBase(filePath, baseDir)).toBe(false);
- });
- test('should return false for directory with similar prefix', () => {
- // /tmp/growi-export-evil should not match /tmp/growi-export
- const filePath = '/tmp/growi-export-evil/test.json';
- expect(isPathWithinBase(filePath, baseDir)).toBe(false);
- });
- });
- });
- describe('isFileNameSafeForBaseDir', () => {
- const baseDir = '/tmp/growi-export';
- describe('valid file names', () => {
- test('should return true for simple filename', () => {
- expect(isFileNameSafeForBaseDir('test.json', baseDir)).toBe(true);
- });
- test('should return true for filename in subdirectory', () => {
- expect(isFileNameSafeForBaseDir('subdir/test.json', baseDir)).toBe(
- true,
- );
- });
- test('should return true for deeply nested file', () => {
- expect(isFileNameSafeForBaseDir('a/b/c/d/test.json', baseDir)).toBe(
- true,
- );
- });
- });
- describe('path traversal attacks', () => {
- test('should return false for ../etc/passwd', () => {
- expect(isFileNameSafeForBaseDir('../etc/passwd', baseDir)).toBe(false);
- });
- test('should return false for ../../etc/passwd', () => {
- expect(isFileNameSafeForBaseDir('../../etc/passwd', baseDir)).toBe(
- false,
- );
- });
- test('should return false for subdir/../../../etc/passwd', () => {
- expect(
- isFileNameSafeForBaseDir('subdir/../../../etc/passwd', baseDir),
- ).toBe(false);
- });
- test('should return false for absolute path outside baseDir', () => {
- expect(isFileNameSafeForBaseDir('/etc/passwd', baseDir)).toBe(false);
- });
- test('should return false for path escaping to sibling directory', () => {
- expect(
- isFileNameSafeForBaseDir('subdir/../../other-dir/file.json', baseDir),
- ).toBe(false);
- });
- });
- describe('edge cases', () => {
- test('should handle empty filename', () => {
- // Empty filename resolves to baseDir itself, which is valid
- expect(isFileNameSafeForBaseDir('', baseDir)).toBe(true);
- });
- test('should handle . (current directory)', () => {
- expect(isFileNameSafeForBaseDir('.', baseDir)).toBe(true);
- });
- test('should handle ./filename', () => {
- expect(isFileNameSafeForBaseDir('./test.json', baseDir)).toBe(true);
- });
- });
- });
- describe('assertFileNameSafeForBaseDir', () => {
- const baseDir = '/tmp/growi-export';
- describe('valid file names (should not throw)', () => {
- test('should not throw for simple filename', () => {
- expect(() => {
- assertFileNameSafeForBaseDir('test.json', baseDir);
- }).not.toThrow();
- });
- test('should not throw for filename in subdirectory', () => {
- expect(() => {
- assertFileNameSafeForBaseDir('subdir/test.json', baseDir);
- }).not.toThrow();
- });
- });
- describe('path traversal attacks (should throw)', () => {
- test('should throw for ../etc/passwd', () => {
- expect(() => {
- assertFileNameSafeForBaseDir('../etc/passwd', baseDir);
- }).toThrow('Invalid file path: path traversal detected');
- });
- test('should throw for ../../etc/passwd', () => {
- expect(() => {
- assertFileNameSafeForBaseDir('../../etc/passwd', baseDir);
- }).toThrow('Invalid file path: path traversal detected');
- });
- test('should throw for absolute path outside baseDir', () => {
- expect(() => {
- assertFileNameSafeForBaseDir('/etc/passwd', baseDir);
- }).toThrow('Invalid file path: path traversal detected');
- });
- test('should throw for path escaping to sibling directory', () => {
- expect(() => {
- assertFileNameSafeForBaseDir(
- 'subdir/../../other-dir/file.json',
- baseDir,
- );
- }).toThrow('Invalid file path: path traversal detected');
- });
- });
- });
- });
|