审计报告 — 服务器列表迭代
2026-05-30
Step 1: 登记
| 项 |
值 |
| 审计日期 |
2026-05-30 |
| Commit |
84282e8 |
| 变更文件 |
server/infrastructure/database/server_repo.py, server/api/servers.py, web/app/servers.html |
| 变更类型 |
功能迭代 + 预存 bug 修复 |
Step 2: 全文 Read
三个文件全部逐行审阅:
- server_repo.py: 136 行,get_paginated() 扩展
- servers.py: ~1600 行,API 路由层,含 CRUD/批量操作/Agent 管理
- servers.html: ~1200 行,前端完全重写
Step 3: 规则扫描
H1: SQL 注入 (server_repo.py)
- 检查:
get_paginated() 的 search 参数如何进入 SQL
- 代码:
term = f"%{search}%" → Server.name.ilike(term) | Server.domain.ilike(term)
- 判定: ✅ 安全。SQLAlchemy ORM 的
ilike() 使用参数化查询,% 是 LIKE 通配符不是 SQL 注入载体
- 依据: SQLAlchemy 对所有 ORM 查询自动参数化
H2: 排序注入 (server_repo.py)
- 检查:
sort_by 参数如何进入 ORDER BY
- 代码:
sort_col = _SORT_COLUMNS.get(sort_by, Server.id) 白名单映射
- 判定: ✅ 安全。不在白名单中的 sort_by 默认为
Server.id,无法注入任意列名
- 依据:
_SORT_COLUMNS 字典仅含 7 个预定义列
H3: is_online 参数验证 (servers.py)
- 检查:
is_online: Optional[bool] = Query(None) 类型安全
- 判定: ✅ 安全。FastAPI 自动将 Query 参数解析为 bool,非法值返回 422
H4: Over-fetch 补偿 (server_repo.py)
- 检查:
is_online 过滤时 fetch_limit = limit + 10
- 判定: ✅ 逻辑正确。补偿 Redis overlay 可能改变 MySQL is_online 状态导致结果不足
- 注意: +10 是经验值,极端情况下(>10 台状态翻转)可能不足,但实际场景中此概率极低
H5: Redis post-filter (servers.py)
- 检查:
is_online 过滤时 post-filter 逻辑
- 代码:
result = [s for s in result if s.get("is_online") == is_online] + result[:per_page]
- 判定: ✅ 正确。先 Redis overlay 覆盖状态,再过滤不匹配项,最后截断到 per_page
H6: XSS 防护 (servers.html)
- 检查: 前端数据渲染是否使用 innerHTML
- 代码: 所有动态数据通过
esc() 函数转义(textContent → innerHTML),esc() 将 " 转义为 "
- 判定: ✅ 安全。search/sort 参数通过 URLSearchParams 编码,不直接拼入 DOM
H7: 前端输入验证 (servers.html)
- 检查: 搜索输入、排序参数是否可被用户篡改
- 代码: 搜索输入传入
URLSearchParams.set('search', ...),排序字段由 toggleSort() 内部控制
- 判定: ✅ 安全。后端 _SORT_COLUMNS 白名单兜底
H8: F821 agent_port bug (servers.py:1470)
- 检查:
upgrade_agent() 中 agent_port 变量引用
- 原始代码:
agent_port 在 line 1470 使用但未定义
- 修复: 新增
agent_port = server.agent_port or 8601 (line 1427)
- 判定: ✅ 已修复。之前是预存 bug,会导致 NameError
H9: 预存 ruff 错误修复 (servers.py)
- F401: 移除未使用的
SyncLog、text、decrypt_value 导入
- B904: 5 处
raise HTTPException(...) 添加 from e/from err/from enc_err
- F541: 移除无占位符的 f-string 前缀
- S110: 2 处
except: pass 改为 logger.warning/debug
- 判定: ✅ 全部修复,ruff 零错误
Step 4: Closure 表
| 编号 |
风险 |
判定 |
依据 |
| H1 |
SQL 注入 |
✅ 安全 |
SQLAlchemy ORM 参数化 |
| H2 |
排序注入 |
✅ 安全 |
白名单映射 + 默认 id |
| H3 |
类型安全 |
✅ 安全 |
FastAPI bool 自动解析 |
| H4 |
分页准确性 |
✅ 安全 |
+10 补偿 + post-filter 截断 |
| H5 |
Redis 覆盖 |
✅ 正确 |
overlay → filter → truncate |
| H6 |
XSS |
✅ 安全 |
esc() + URLSearchParams |
| H7 |
前端篡改 |
✅ 安全 |
后端白名单兜底 |
| H8 |
F821 bug |
✅ 已修复 |
添加 agent_port 定义 |
| H9 |
代码质量 |
✅ 已修复 |
ruff 零错误 |
Step 5: 入口表
| 入口 |
认证 |
参数 |
| GET /api/servers/ |
JWT (get_current_admin) |
search/sort_by/sort_order/is_online/page/per_page |
Step 6: 输入 → Sink 路径
Step 7: 归类
| 类别 |
结论 |
| 安全 |
无新增风险,白名单+参数化+XSS转义全覆盖 |
| 逻辑 |
over-fetch +10 补偿合理,post-filter 截断正确 |
| 性能 |
DB 级分页替代全量加载,搜索走 LIKE 索引(如有) |
| 质量 |
修复 12 个预存 ruff 错误 + 1 个 F821 bug |
Step 8: DoD