fix: 修复代码审查中发现的关键问题

1. 修复方法名错误
   - 将 performScheduledHealthChecks() 改为 performHealthChecks()
   - 修复启动时健康检查调用错误

2. 改进路径遍历防护
   - 仅在 Windows 平台执行小写转换
   - 避免 Linux 平台路径大小写误判

3. 增强 XSS 防护
   - 扩展危险协议检测(data/javascript/vbscript)
   - 使用更安全的 HTML 标签移除方式
   - 添加 HTML 实体编码攻击防护

4. 为文件锁添加超时机制
   - 防止操作永久挂起导致锁链阻塞
   - 默认超时时间 30 秒
This commit is contained in:
Wenaixi 2026-04-03 01:37:53 +08:00
parent 1018750388
commit bc018cf16f
3 changed files with 35 additions and 14 deletions

View file

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

View file

@ -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) {

View file

@ -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;
// 移除 <script>...</script>(支持跨行匹配)
name = name.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/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);