Files
Nexus/docs/changelog/2026-06-02-medium-bug-fix-6.md
T

93 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-06-02 MEDIUM 级 Bug 修复(6 项)
## 日期
2026-06-02
## 变更摘要
修复系统扫描中发现的 6 个 MEDIUM 级 Bug,提升安全性、数据准确性和系统健壮性。
---
## M1: 登录锁定按 username+ip 计数,可换 IP 绕过
**文件**: `server/infrastructure/database/admin_repo.py`
**问题**: `count_recent_failures` 的 WHERE 条件同时过滤 `username AND ip_address`,攻击者只需更换 IP 即可无限尝试同一账号密码。
**修复**: 移除 `ip_address` 维度,改为仅按 `username` 计数失败次数。换 IP 不再能绕过锁定。
---
## M2: 重复 PushRetryJob 创建(同一服务器多条 pending)
**文件**: `server/application/services/sync_engine_v2.py`
**问题**: 每次 push 失败都无条件创建新的 `PushRetryJob`,若多次触发同一路径的推送且反复失败,会堆积大量重复 pending 任务。
**修复**: 创建前先查询是否已存在相同 `(server_id, source_path, target_path, status=pending)` 的记录,存在则跳过创建。
---
## M3: schedule_runner 先标记已执行再同步,失败时调度记录丢失
**文件**: `server/background/schedule_runner.py`
**问题**: 执行前先将 `last_run_at = now``commit()`,若同步任务抛出异常,`last_run_at` 已更新,该次调度不会重新触发,相当于"静默跳过"了一次调度。
**修复**: 保存 `prev_last_run_at`,执行失败时将 `last_run_at` 回滚,允许下一个周期重新触发。`once` 模式不回滚(已标记 `enabled=False`,防止重复执行)。
---
## M4: servers.py is_online 过滤后 total 仍用 DB 总数,分页不准
**文件**: `server/api/servers.py`
**问题**: 当传入 `is_online` 过滤参数时,Redis 覆盖后的内存过滤结果与 DB 总数不一致,`total``pages` 字段仍反映 DB 全量数量,前端分页逻辑错乱。
**修复**: 内存过滤后重新计算 `total = len(result)``pages` 同步更新。
---
## M5: asyncssh_pool exec_ssh_command 超时后未踢出坏连接
**文件**: `server/infrastructure/ssh/asyncssh_pool.py`
**问题**: `asyncio.TimeoutError``finally` 中仍会 `release()` 超时的连接,将其放回连接池,下一次 `acquire()` 可能复用这个状态未知的坏连接,导致命令执行结果不可靠。
**修复**: 超时和连接错误时改为 `close_connection()` 而非 `release()`,将坏连接从池中彻底移除;一般异常仍 `release()`(可能仍可用)。
---
## M6: token_version += 1 在 NULL 时 TypeError
**文件**: `server/api/auth.py`, `server/application/services/auth_service.py`3 处)
**问题**: DB 中 `token_version``NULL`(直接 INSERT 未设默认值时)时,`admin.token_version += 1``TypeError: unsupported operand type(s) for +=: 'NoneType' and 'int'`,导致修改密码 / 禁用 TOTP / 强制登出接口 500。
**修复**: 所有 `token_version += 1` 统一改为 `(token_version or 0) + 1`,共修复 3 处。
---
## 涉及文件
| 文件 | 修复项 |
|------|-------|
| `server/infrastructure/database/admin_repo.py` | M1(锁定维度) |
| `server/application/services/sync_engine_v2.py` | M2(重复 RetryJob |
| `server/background/schedule_runner.py` | M3(调度回滚) |
| `server/api/servers.py` | M4(分页 total |
| `server/infrastructure/ssh/asyncssh_pool.py` | M5(超时踢连接) |
| `server/api/auth.py` | M6token_version None |
| `server/application/services/auth_service.py` | M6token_version None2 处) |
## 需要迁移/重启
- 后端重启生效(`supervisorctl restart nexus`
- 无数据库 schema 变更
## 验证方式
1. `python -c "import server.main"` — 导入无报错 ✓
2. 同一 IP 登录失败 5 次后,换 IP 仍被锁定
3. 查看 `push_retry_jobs` 表,相同路径不再出现多条 pending
4. 调度执行失败后 `last_run_at` 应保持原值
5. 服务器列表按在线状态过滤,分页 total 与实际条目数一致