修复37项安全漏洞和Bug

高危修复:
- app.py: 添加ip_rate_limit_lock线程锁保护IP限流字典
- app.py: 添加validate_ip_port()验证代理IP/端口范围
- database.py: SQL字段名白名单验证防止注入
- playwright_automation.py: 改进浏览器进程强制清理逻辑

中危修复:
- database.py: 统一时区处理函数get_cst_now()
- database.py: 消除循环导入,移动app_security导入到顶部
- playwright_automation.py: 所有bare except改为except Exception
- app_config.py: dotenv导入失败警告+安全配置检查
- db_pool.py: 添加详细异常堆栈日志
- app_security.py: 用户名过滤零宽字符
- database.py: delete_old_task_logs分批删除避免锁表

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-11 19:35:29 +08:00
parent a25c9fbba0
commit 126d4cbb52
6 changed files with 290 additions and 96 deletions

View File

@@ -227,7 +227,8 @@ class PlaywrightAutomation:
if 'index.aspx' in current_url:
return True
return False
except:
except Exception:
# Bug fix: 明确捕获Exception而非所有异常
return False
def quick_login(self, username: str, password: str, remember: bool = True):
@@ -258,8 +259,8 @@ class PlaywrightAutomation:
self.browser.close()
if self.playwright:
self.playwright.stop()
except:
pass
except Exception:
pass # 清理时忽略错误
# 正常登录
result = self.login(username, password, remember)
@@ -359,8 +360,8 @@ class PlaywrightAutomation:
error_type = "password_error"
else:
error_type = "login_error"
except:
pass
except Exception:
pass # 获取错误提示失败时忽略
# 如果没有明确的错误提示,可能是网络问题,不认为是密码错误
if error_type == "unknown":
@@ -476,11 +477,11 @@ class PlaywrightAutomation:
time.sleep(1.5)
try:
self.main_page.wait_for_load_state('domcontentloaded', timeout=5000)
except:
except Exception: # Bug fix: 明确捕获Exception
pass
try:
self.main_page.wait_for_load_state('networkidle', timeout=10000)
except:
except Exception: # Bug fix: 明确捕获Exception
pass
self.page = self.get_iframe_safe(retry=True, max_retries=3)
@@ -529,10 +530,10 @@ class PlaywrightAutomation:
# 等待表格加载
try:
self.page.locator("//table[@class='ltable']").wait_for(timeout=10000)
except:
except Exception: # Bug fix: 明确捕获Exception
pass
self.log(f"✓ iframe恢复成功刷新后重新点击'{browse_type}'")
except:
except Exception: # Bug fix: 明确捕获Exception
# 尝试点击label
try:
label_selector = f"//label[contains(text(), '{browse_type}')]"
@@ -777,7 +778,7 @@ class PlaywrightAutomation:
# 等待表格加载完成最多等待10秒
try:
self.page.locator("//table[@class='ltable']").wait_for(timeout=10000)
except:
except Exception: # Bug fix: 明确捕获Exception
self.log("等待表格超时,继续尝试...")
# 额外等待确保AJAX内容加载完成
@@ -912,7 +913,7 @@ class PlaywrightAutomation:
if match:
expected_total = int(match.group(1))
self.log(f"[总数] 预期浏览 {expected_total} 条内容")
except:
except Exception: # Bug fix: 明确捕获Exception
pass
# 处理每一行 (每次从头重新获取所有行)
@@ -1026,7 +1027,7 @@ class PlaywrightAutomation:
try:
current_rows_locator = self.page.locator("//table[@class='ltable']/tbody/tr[position()>1 and count(td)>=5]")
row = current_rows_locator.nth(i)
except:
except Exception: # Bug fix: 明确捕获Exception
break
else:
break
@@ -1085,7 +1086,7 @@ class PlaywrightAutomation:
# 关闭新窗口
try:
new_page.close()
except:
except Exception: # Bug fix: 明确捕获Exception
pass
self.log(f" - 新窗口已关闭")
else:
@@ -1134,7 +1135,7 @@ class PlaywrightAutomation:
self.page = self.get_iframe_safe()
if not self.page:
self.recover_iframe(browse_type)
except:
except Exception: # Bug fix: 明确捕获Exception
pass
# 处理完当前页后,检查是否需要翻页
@@ -1212,7 +1213,7 @@ class PlaywrightAutomation:
try:
self.page.locator("//table[@class='ltable']").wait_for(timeout=30000)
self.log("内容表格已加载")
except:
except Exception: # Bug fix: 明确捕获Exception
self.log("等待表格加载超时,继续...")
except Exception as e:
if self.is_context_error(str(e)):
@@ -1388,26 +1389,68 @@ class PlaywrightAutomation:
# else部分日志已精简
def _cleanup_on_exit(self):
"""进程退出时的清理函数由atexit调用"""
# Bug #13 fix: 尝试获取锁,但不阻塞(避免退出时死锁)
"""进程退出时的清理函数由atexit调用
Bug fix: 改进清理逻辑,即使锁获取失败也尝试清理资源
"""
# 尝试获取锁,但不阻塞(避免退出时死锁)
acquired = self._lock.acquire(blocking=False)
try:
if not self._closed:
try:
# 静默关闭,避免在退出时产生过多日志
if self.context:
self.context.close()
if self.browser:
self.browser.close()
if self.playwright:
self.playwright.stop()
self._closed = True
except:
pass # 退出时忽略所有错误
self._force_cleanup()
finally:
if acquired:
self._lock.release()
def _force_cleanup(self):
"""强制清理资源(不依赖锁状态)
Bug fix: 添加进程级清理,确保浏览器进程被终止
"""
import subprocess
import sys
# 记录浏览器进程ID用于强制清理
browser_pid = None
try:
if self.browser and hasattr(self.browser, '_impl_obj'):
# 尝试获取浏览器进程ID
impl = self.browser._impl_obj
if hasattr(impl, '_browser_process') and impl._browser_process:
browser_pid = impl._browser_process.pid
except Exception:
pass
# 尝试正常关闭
try:
if self.context:
self.context.close()
except Exception:
pass
try:
if self.browser:
self.browser.close()
except Exception:
pass
try:
if self.playwright:
self.playwright.stop()
except Exception:
pass
# 如果有浏览器进程ID且在Linux/Mac上强制杀死进程
if browser_pid and sys.platform != 'win32':
try:
import os
import signal
os.kill(browser_pid, signal.SIGKILL)
except (ProcessLookupError, PermissionError, OSError):
pass # 进程可能已经退出
self._closed = True
def __enter__(self):
"""Context manager支持 - 进入"""
return self