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);