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

135 lines
5.3 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-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() 将 `"` 转义为 `&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**: 移除未使用的 `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 路径
```
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
- [x] ruff check: All checks passed
- [x] bandit: No issues identified
- [x] import server.main: 成功
- [x] 无 TODO/FIXME
- [x] 无静默吞错(S110 全部改为 logger.warning/debug
- [x] 审计日志: list_servers 是 GET 端点,无需审计(只读)
- [x] Playwright 浏览器验证: 搜索/排序/筛选/分页全部通过
- [x] 服务器部署成功 + 健康检查通过