From bc018cf16fba29955c810ac8946a8ff407f178c8 Mon Sep 17 00:00:00 2001 From: Wenaixi Date: Fri, 3 Apr 2026 01:37:53 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=AE=A1=E6=9F=A5=E4=B8=AD=E5=8F=91=E7=8E=B0=E7=9A=84=E5=85=B3?= =?UTF-8?q?=E9=94=AE=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 修复方法名错误 - 将 performScheduledHealthChecks() 改为 performHealthChecks() - 修复启动时健康检查调用错误 2. 改进路径遍历防护 - 仅在 Windows 平台执行小写转换 - 避免 Linux 平台路径大小写误判 3. 增强 XSS 防护 - 扩展危险协议检测(data/javascript/vbscript) - 使用更安全的 HTML 标签移除方式 - 添加 HTML 实体编码攻击防护 4. 为文件锁添加超时机制 - 防止操作永久挂起导致锁链阻塞 - 默认超时时间 30 秒 --- src/services/api-server.js | 2 +- src/ui-modules/config-api.js | 14 ++++++++------ src/ui-modules/provider-api.js | 33 ++++++++++++++++++++++++++------- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/services/api-server.js b/src/services/api-server.js index 4793776..5e0da81 100644 --- a/src/services/api-server.js +++ b/src/services/api-server.js @@ -428,7 +428,7 @@ async function startServer() { logger.info('[ScheduledHealthCheck] Running scheduled health check on startup...'); setTimeout(async () => { try { - await poolManager.performScheduledHealthChecks(); + await poolManager.performHealthChecks(); } catch (error) { logger.error('[ScheduledHealthCheck] Startup run error:', error); } diff --git a/src/ui-modules/config-api.js b/src/ui-modules/config-api.js index b6a796c..d39b028 100644 --- a/src/ui-modules/config-api.js +++ b/src/ui-modules/config-api.js @@ -134,9 +134,10 @@ export async function handleUpdateConfig(req, res, currentConfig) { const relativePath = path.relative(cwd, resolved); const isInsideCwd = !path.isAbsolute(relativePath) && !relativePath.startsWith('..') && relativePath !== '..'; - // Windows 大小写不敏感兼容:统一转换为小写比较 - const normalizedResolved = resolved.toLowerCase().replace(/\\/g, '/'); - const normalizedCwd = cwd.toLowerCase().replace(/\\/g, '/'); + // Windows 大小写不敏感兼容:仅在 Windows 平台统一转换为小写比较 + const isWindows = process.platform === 'win32'; + const normalizedResolved = (isWindows ? resolved.toLowerCase() : resolved).replace(/\\/g, '/'); + const normalizedCwd = (isWindows ? cwd.toLowerCase() : cwd).replace(/\\/g, '/'); const startsWithCwd = normalizedResolved.startsWith(normalizedCwd + '/') || normalizedResolved === normalizedCwd; if (isInsideCwd && startsWithCwd) { @@ -188,9 +189,10 @@ export async function handleUpdateConfig(req, res, currentConfig) { const relativePath = path.relative(cwd, resolved); const isInsideCwd = !path.isAbsolute(relativePath) && !relativePath.startsWith('..') && relativePath !== '..'; - // Windows 大小写不敏感兼容:统一转换为小写比较 - const normalizedResolved = resolved.toLowerCase().replace(/\\/g, '/'); - const normalizedCwd = cwd.toLowerCase().replace(/\\/g, '/'); + // Windows 大小写不敏感兼容:仅在 Windows 平台统一转换为小写比较 + const isWindows = process.platform === 'win32'; + const normalizedResolved = (isWindows ? resolved.toLowerCase() : resolved).replace(/\\/g, '/'); + const normalizedCwd = (isWindows ? cwd.toLowerCase() : cwd).replace(/\\/g, '/'); const startsWithCwd = normalizedResolved.startsWith(normalizedCwd + '/') || normalizedResolved === normalizedCwd; if (isInsideCwd && startsWithCwd) { diff --git a/src/ui-modules/provider-api.js b/src/ui-modules/provider-api.js index 09beaf4..1d4cf3d 100644 --- a/src/ui-modules/provider-api.js +++ b/src/ui-modules/provider-api.js @@ -14,14 +14,22 @@ function sanitizeProviderData(provider) { const sanitized = { ...provider }; if (typeof sanitized.customName === 'string') { let name = sanitized.customName; - // 拒绝包含 data: 协议(可能包含内嵌恶意内容) - if (/data\s*:/i.test(name)) return sanitized; - // 移除 (支持跨行匹配) - name = name.replace(/)<[^<]*)*<\/script>/gi, ''); + + // 拒绝包含危险协议 + if (/(?:data|javascript|vbscript)\s*:/i.test(name)) { + sanitized.customName = ''; + return sanitized; + } + + // 移除所有 HTML 标签(更安全的方式) + name = name.replace(/<[^>]*>/g, ''); + // 移除 HTML 事件处理器属性(onclick/onerror 等) name = name.replace(/\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]*)/gi, ''); - // 移除 javascript: 协议(更严格:要求独立单词边界,防止误匹配 "not javascript code") - name = name.replace(/\bjavascript\s*:/gi, ''); + + // 移除潜在的 HTML 实体编码攻击 + name = name.replace(/&[#\w]+;/g, ''); + sanitized.customName = name.trim(); } return sanitized; @@ -39,9 +47,20 @@ function sanitizeProviderPools(pools) { } // 使用 Promise 链式队列,确保文件操作顺序执行 let _fileLockChain = Promise.resolve(); + +// 超时包装函数:防止操作永久挂起导致锁链阻塞 +function withTimeout(promise, ms = 30000) { + return Promise.race([ + promise, + new Promise((_, reject) => + setTimeout(() => reject(new Error(`Operation timeout after ${ms}ms`)), ms) + ) + ]); +} + function withFileLock(fn) { const next = _fileLockChain - .then(() => fn()) + .then(() => withTimeout(fn(), 30000)) .catch(err => { // 记录错误并抛出,中断操作 logger.error('[FileLock] Operation failed:', err?.message || err);