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 path from 'path';
|
||||||
import { RESOURCE_PATH } from './constants';
|
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[]> {
|
export async function getProjects(): Promise<string[]> {
|
||||||
const dirs = await fs.readdir(RESOURCE_PATH);
|
const dirs = await fs.readdir(RESOURCE_PATH);
|
||||||
const projectDirs = dirs.filter(dir => {
|
const projectDirs = await Promise.all(
|
||||||
const dirPath = path.join(RESOURCE_PATH, dir);
|
dirs.map(async (dir) => {
|
||||||
// 检查是否是目录
|
const dirPath = validatePath(RESOURCE_PATH, dir);
|
||||||
return fs.stat(dirPath).then(stats => stats.isDirectory()).catch(() => false);
|
try {
|
||||||
});
|
const stats = await fs.stat(dirPath);
|
||||||
return projectDirs;
|
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[]> {
|
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 dirs = await fs.readdir(projectPath);
|
||||||
const versionDirs = dirs.filter(dir => {
|
const versionDirs = await Promise.all(
|
||||||
const dirPath = path.join(projectPath, dir);
|
dirs.map(async (dir) => {
|
||||||
return fs.stat(dirPath).then(stats => stats.isDirectory()).catch(() => false);
|
const dirPath = validatePath(projectPath, dir);
|
||||||
});
|
try {
|
||||||
return versionDirs.sort();
|
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;
|
size: number;
|
||||||
modifiedAt: Date;
|
modifiedAt: Date;
|
||||||
}[]> {
|
}[]> {
|
||||||
const apksPath = path.join(RESOURCE_PATH, project, version, 'apks');
|
const apksPath = validatePath(RESOURCE_PATH, project, version, 'apks');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const files = await fs.readdir(apksPath);
|
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(
|
const apks = await Promise.all(
|
||||||
apkFiles.map(async (file) => {
|
apkFiles.map(async (file) => {
|
||||||
const filePath = path.join(apksPath, file);
|
const filePath = validatePath(apksPath, file);
|
||||||
const stats = await fs.stat(filePath);
|
const stats = await fs.stat(filePath);
|
||||||
|
|
||||||
// 提取 commit id (假设文件名格式为 xxx-{commit}-xxx.apk)
|
// 提取 commit id (假设文件名格式为 xxx-{commit}-xxx.apk)
|
||||||
|
|||||||
Reference in New Issue
Block a user