Skip to content

Commit 518e8b5

Browse files
improved fix
1 parent 3d6556d commit 518e8b5

3 files changed

Lines changed: 37 additions & 2 deletions

File tree

packages/web/src/features/fileTree/api.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { createLogger } from '@sourcebot/shared';
99
import path from 'path';
1010
import { simpleGit } from 'simple-git';
1111
import { FileTreeItem } from './types';
12-
import { buildFileTree, normalizePath } from './utils';
12+
import { buildFileTree, isPathValid, normalizePath } from './utils';
1313
import { compareFileTreeItems } from './utils';
1414

1515
const logger = createLogger('file-tree');
@@ -36,6 +36,10 @@ export const getTree = async (params: { repoName: string, revisionName: string,
3636
const { path: repoPath } = getRepoPath(repo);
3737

3838
const git = simpleGit().cwd(repoPath);
39+
if (!paths.every(path => isPathValid(path))) {
40+
return notFound();
41+
}
42+
3943
const normalizedPaths = paths.map(path => normalizePath(path));
4044

4145
let result: string = '';
@@ -100,6 +104,9 @@ export const getFolderContents = async (params: { repoName: string, revisionName
100104
const { path: repoPath } = getRepoPath(repo);
101105
const git = simpleGit().cwd(repoPath);
102106

107+
if (!isPathValid(path)) {
108+
return notFound();
109+
}
103110
const normalizedPath = normalizePath(path);
104111

105112
let result: string;

packages/web/src/features/fileTree/utils.test.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, test } from 'vitest';
2-
import { buildFileTree, normalizePath } from './utils';
2+
import { buildFileTree, isPathValid, normalizePath } from './utils';
33

44
test('normalizePath adds a trailing slash and strips leading slashes', () => {
55
expect(normalizePath('/a/b')).toBe('a/b/');
@@ -13,6 +13,23 @@ test('normalizePath returns empty string for root', () => {
1313
expect(normalizePath('/')).toBe('');
1414
});
1515

16+
test('isPathValid rejects traversal and null bytes', () => {
17+
expect(isPathValid('a/../b')).toBe(false);
18+
expect(isPathValid('a/\0b')).toBe(false);
19+
});
20+
21+
test('isPathValid allows normal paths', () => {
22+
expect(isPathValid('a/b')).toBe(true);
23+
});
24+
25+
test('isPathValid allows paths with dots', () => {
26+
expect(isPathValid('a/b/c.txt')).toBe(true);
27+
expect(isPathValid('a/b/c...')).toBe(true);
28+
expect(isPathValid('a/b/c.../d')).toBe(true);
29+
expect(isPathValid('a/b/..c')).toBe(true);
30+
expect(isPathValid('a/b/[..path]')).toBe(true);
31+
});
32+
1633
test('buildFileTree handles a empty flat list', () => {
1734
const flatList: { type: string, path: string }[] = [];
1835
const tree = buildFileTree(flatList);

packages/web/src/features/fileTree/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ export const normalizePath = (path: string): string => {
2121
return normalizedPath;
2222
}
2323

24+
// @note: we don't allow directory traversal
25+
// or null bytes in the path.
26+
export const isPathValid = (path: string) => {
27+
const pathSegments = path.split('/');
28+
if (pathSegments.some(segment => segment === '..') || path.includes('\0')) {
29+
return false;
30+
}
31+
32+
return true;
33+
}
34+
2435
export const buildFileTree = (flatList: { type: string, path: string }[]): FileTreeNode => {
2536
const root: FileTreeNode = {
2637
name: 'root',

0 commit comments

Comments
 (0)