fix(ql3): harden legacy api compatibility

This commit is contained in:
whyour
2026-08-20 19:09:21 +08:00
parent 3c02ae2020
commit 4ea156f189
7 changed files with 867 additions and 7 deletions
+16 -3
View File
@@ -1,4 +1,11 @@
import { fileExist, readDirs, readDir, rmPath, IFile } from '../config/util';
import {
fileExist,
isPathInside,
readDirs,
readDir,
rmPath,
IFile,
} from '../config/util';
import { Router, Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
import { Logger } from 'winston';
@@ -14,7 +21,9 @@ const route = Router();
function isPathAllowed(targetPath: string): boolean {
const resolved = path.resolve(targetPath);
return config.writePathList.some((x) => resolved.startsWith(x));
return config.writePathList.some((rootPath) =>
isPathInside(rootPath, resolved),
);
}
const storage = multer.diskStorage({
@@ -158,7 +167,11 @@ export default (app: Router) => {
if (!path.startsWith('/')) {
path = join(config.scriptPath, path);
}
if (config.writePathList.every((x) => !path.startsWith(x))) {
if (
config.writePathList.every(
(rootPath) => !isPathInside(rootPath, path),
)
) {
return res.send({
code: 403,
message: t('暂无权限'),
+28 -2
View File
@@ -263,13 +263,26 @@ export async function readDir(
baseDir: string = '',
blacklist: string[] = [],
): Promise<IFile[]> {
const absoluteBaseDir = path.resolve(baseDir);
const absoluteDir = path.resolve(baseDir, dir);
if (!absoluteDir.startsWith(path.resolve(baseDir))) {
if (!isPathInside(absoluteBaseDir, absoluteDir)) {
return [];
}
const relativePath = path.relative(baseDir, absoluteDir);
try {
const [realBaseDir, realDirectory] = await Promise.all([
fs.realpath(absoluteBaseDir),
fs.realpath(absoluteDir),
]);
const directoryStat = await fs.lstat(absoluteDir);
if (
!isPathInside(realBaseDir, realDirectory) ||
!directoryStat.isDirectory() ||
directoryStat.isSymbolicLink()
) {
return [];
}
const relativePath = path.relative(absoluteBaseDir, absoluteDir);
const files = await fs.readdir(absoluteDir);
const result: IFile[] = [];
@@ -312,6 +325,19 @@ export async function readDir(
}
}
export function isPathInside(rootPath: string, targetPath: string): boolean {
const relative = path.relative(
path.resolve(rootPath),
path.resolve(targetPath),
);
return (
relative === '' ||
(relative !== '..' &&
!relative.startsWith(`..${path.sep}`) &&
!path.isAbsolute(relative))
);
}
export async function promiseExec(command: string): Promise<string> {
try {
const { stderr, stdout } = await promisify(exec)(command, {
+8 -2
View File
@@ -6,7 +6,13 @@ import CronService from './cron';
import ScheduleService, { TaskCallbacks } from './schedule';
import config from '../config';
import { TASK_COMMAND } from '../config/const';
import { getFileContentByName, getPid, killTask, rmPath } from '../config/util';
import {
getFileContentByName,
getPid,
isPathInside,
killTask,
rmPath,
} from '../config/util';
import taskLimit from '../shared/pLimit';
@Service()
@@ -66,7 +72,7 @@ export default class ScriptService {
public checkFilePath(filePath: string, fileName: string) {
const finalPath = path.resolve(config.scriptPath, filePath, fileName);
return finalPath.startsWith(config.scriptPath) ? finalPath : '';
return isPathInside(config.scriptPath, finalPath) ? finalPath : '';
}
public async getFile(filePath: string, fileName: string) {