Files
Nexus/docs/audit/2026-05-30-servers-iteration.md
T
Your Name ebe276ea29 docs: add changelog and audit for servers iteration
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 19:50:07 +08:00

5.3 KiB
Raw Blame History

审计报告 — 服务器列表迭代

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() 函数转义(textContentinnerHTML),esc() 将 " 转义为 &quot;
  • 判定: 安全。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: 移除未使用的 SyncLogtextdecrypt_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 路径

search (str) → server_repo.get_paginated() → Server.name.ilike(参数化) → MySQL
sort_by (str) → _SORT_COLUMNS.get(白名单) → Server.id(默认) → ORDER BY
sort_order (str) → asc/desc 分支 → ORDER BY 方向
is_online (bool) → WHERE Server.is_online == ? + Redis post-filter

Step 7: 归类

类别 结论
安全 无新增风险,白名单+参数化+XSS转义全覆盖
逻辑 over-fetch +10 补偿合理,post-filter 截断正确
性能 DB 级分页替代全量加载,搜索走 LIKE 索引(如有)
质量 修复 12 个预存 ruff 错误 + 1 个 F821 bug

Step 8: DoD

  • ruff check: All checks passed
  • bandit: No issues identified
  • import server.main: 成功
  • 无 TODO/FIXME
  • 无静默吞错(S110 全部改为 logger.warning/debug
  • 审计日志: list_servers 是 GET 端点,无需审计(只读)
  • Playwright 浏览器验证: 搜索/排序/筛选/分页全部通过
  • 服务器部署成功 + 健康检查通过