fix: address race conditions and add path validation
- Fixed race condition in getProjects() by using Promise.all with proper async/await - Fixed race condition in getVersions() by using Promise.all with proper async/await - Fixed race condition in getApks() by ensuring Promise.all wraps the async map - Added validatePath() helper function to prevent path traversal attacks - Applied path validation to all file system operations Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,30 +3,55 @@ import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { RESOURCE_PATH } from './constants';
|
||||
|
||||
/**
|
||||
* Validate that a path doesn't escape the base directory (prevents path traversal)
|
||||
*/
|
||||
function validatePath(basePath: string, ...segments: string[]): string {
|
||||
const fullPath = path.resolve(basePath, ...segments);
|
||||
const normalizedBase = path.resolve(basePath);
|
||||
if (!fullPath.startsWith(normalizedBase)) {
|
||||
throw new Error('Invalid path: path traversal detected');
|
||||
}
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有项目目录
|
||||
*/
|
||||
export async function getProjects(): Promise<string[]> {
|
||||
const dirs = await fs.readdir(RESOURCE_PATH);
|
||||
const projectDirs = dirs.filter(dir => {
|
||||
const dirPath = path.join(RESOURCE_PATH, dir);
|
||||
// 检查是否是目录
|
||||
return fs.stat(dirPath).then(stats => stats.isDirectory()).catch(() => false);
|
||||
});
|
||||
return projectDirs;
|
||||
const projectDirs = await Promise.all(
|
||||
dirs.map(async (dir) => {
|
||||
const dirPath = validatePath(RESOURCE_PATH, dir);
|
||||
try {
|
||||
const stats = await fs.stat(dirPath);
|
||||
return stats.isDirectory() ? dir : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
return projectDirs.filter((dir): dir is string => dir !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定项目的所有版本目录
|
||||
*/
|
||||
export async function getVersions(project: string): Promise<string[]> {
|
||||
const projectPath = path.join(RESOURCE_PATH, project);
|
||||
const projectPath = validatePath(RESOURCE_PATH, project);
|
||||
const dirs = await fs.readdir(projectPath);
|
||||
const versionDirs = dirs.filter(dir => {
|
||||
const dirPath = path.join(projectPath, dir);
|
||||
return fs.stat(dirPath).then(stats => stats.isDirectory()).catch(() => false);
|
||||
});
|
||||
return versionDirs.sort();
|
||||
const versionDirs = await Promise.all(
|
||||
dirs.map(async (dir) => {
|
||||
const dirPath = validatePath(projectPath, dir);
|
||||
try {
|
||||
const stats = await fs.stat(dirPath);
|
||||
return stats.isDirectory() ? dir : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
return versionDirs.filter((dir): dir is string => dir !== null).sort();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,7 +63,7 @@ export async function getApks(project: string, version: string, commitId?: strin
|
||||
size: number;
|
||||
modifiedAt: Date;
|
||||
}[]> {
|
||||
const apksPath = path.join(RESOURCE_PATH, project, version, 'apks');
|
||||
const apksPath = validatePath(RESOURCE_PATH, project, version, 'apks');
|
||||
|
||||
try {
|
||||
const files = await fs.readdir(apksPath);
|
||||
@@ -46,7 +71,7 @@ export async function getApks(project: string, version: string, commitId?: strin
|
||||
|
||||
const apks = await Promise.all(
|
||||
apkFiles.map(async (file) => {
|
||||
const filePath = path.join(apksPath, file);
|
||||
const filePath = validatePath(apksPath, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
|
||||
// 提取 commit id (假设文件名格式为 xxx-{commit}-xxx.apk)
|
||||
|
||||
Reference in New Issue
Block a user