@ -392,7 +392,7 @@ export class ProjectController {
);
// 设置CSP头
ctx.set('Content-Security-Policy', "default-src 'self'; script-src 'unsafe-inline' 'unsafe-eval' 'self'; style-src 'unsafe-inline' 'self';");
ctx.set('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data:; worker-src 'self' blob: data:; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline'; connect-src 'self' blob: data:; object-src 'none'; base-uri 'self';");
ctx.set('Content-Type', 'text/html; charset=utf-8');
ctx.body = htmlContent;
} catch (error: any) {
@ -84,12 +84,19 @@ export async function extractZip(zipPath: string, extractTo: string): Promise<vo
zipfile.on('entry', (entry: yauzl.Entry) => {
// 检查文件名安全性
const sanitized = sanitizeFileName(entry.fileName);
if (sanitized !== entry.fileName) {
// 检查路径遍历攻击(..)和危险字符,但允许正常的目录结构
if (entry.fileName.includes('..') ||
/[<>:"|?*\x00-\x1f]/.test(entry.fileName)) {
reject(new Error('ZIP文件包含不安全的文件名'));
return;
}
// 检查绝对路径(Windows: C:\ 或 Unix: /)
if (/^([a-zA-Z]:|\\\\|\/)/.test(entry.fileName)) {
reject(new Error('ZIP文件包含绝对路径'));
// 检查解压后总大小(防止zip炸弹)
totalSize += entry.uncompressedSize;
if (totalSize > MAX_EXTRACT_SIZE) {