# Nexus 6.0 全量代码审查报告

**审查日期**: 2026-05-23  
**审查方式**: 7 部门并行审计（安全/基础设施/API路由/业务服务/实时系统/前端/部署）

> **Phase 2 修复状态（2026-05-23）**  
> 本报告为 **初审问题清单**。Phase 2 逐行走读已对其中大量项落地修复。  
> **C1–C12 对照**：[`audit-full-vs-phase2-reconciliation.md`](audit-full-vs-phase2-reconciliation.md)  
> **开发 SSOT**：[`docs/project/phase-2-audit-remediation.md`](../project/phase-2-audit-remediation.md)  
> **全量 ID 状态**：[`audit-phase-2-findings-matrix.md`](audit-phase-2-findings-matrix.md)（✅52 / 📋7 / 🟡3 / 代码待办 0）

**审查原则**: 零容忍 — 不允许降级实现、不允许留技术债、发现问题必须当场修复

---

## 总览

| 严重级别 | 数量 | 说明 |
|----------|------|------|
| CRITICAL | 12 | 必须立即修复，存在安全漏洞或数据损坏风险 |
| HIGH | 18 | 尽快修复，存在安全隐患或功能缺陷 |
| MEDIUM | 20 | 应修复，影响可维护性或可靠性 |
| LOW | 15+ | 建议修复，代码质量改进 |

---

## CRITICAL 级别问题（12 个）

### C1. `/logout` 端点无身份验证
- **文件**: `server/api/auth.py:82-89`
- **问题**: `/logout` 路由没有 `Depends(get_current_admin)`，任何人都可以 POST 任意 `admin_id` 强制注销管理员
- **影响**: 攻击者可枚举 admin ID 批量强制所有管理员下线
- **修复**: 添加 JWT 认证依赖，验证请求者身份

### C2. 所有审计日志缺少 `admin_username` 和 `ip_address`
- **文件**: `server/application/services/auth_service.py:273`
- **问题**: `_audit()` 方法硬编码 `ip_address=""` 且从不传递 `admin_username`，所有审计记录的这两个字段永远为空
- **影响**: 审计日志无法回答"谁做了什么"和"从哪里做的"
- **修复**: 修改 `_audit()` 签名接受这两个参数，所有调用点传递实际值

### C3. SHA-256 密码验证回退
- **文件**: `server/application/services/auth_service.py:238-245`
- **问题**: bcrypt 导入失败时静默回退到无盐 SHA-256，易受彩虹表攻击
- **影响**: 如果 bcrypt 库损坏或未安装，所有密码验证降级为不安全方式
- **修复**: 删除回退，bcrypt 不可用时直接抛出异常

### C4. 所有模型时间戳在导入时冻结
- **文件**: `server/domain/models/__init__.py`（影响全部 14 张表）
- **问题**: `default=datetime.datetime.now(timezone.utc)` 在模块导入时**调用**，返回的单一 datetime 对象被存储为默认值。所有新行获得相同的冻结时间戳
- **影响**: `created_at`、`updated_at` 字段完全不可靠，时间查询和审计追踪失效
- **受影响列**: Platform.created_at, Node.created_at, Server.created_at, Server.updated_at, SyncLog.started_at, Admin.created_at, LoginAttempt.attempted_at, Setting.updated_at, PasswordPreset.created_at, PushSchedule.created_at, PushRetryJob.created_at, PushRetryJob.updated_at, AuditLog.created_at, Script.created_at, Script.updated_at, ScriptExecution.started_at, DbCredential.created_at, SshSession.started_at, CommandLog.created_at
- **修复**: 改为 `default=lambda: datetime.datetime.now(timezone.utc)` 或 `default=func.now()`

### C5. `sync_service.py` 批量推送崩溃
- **文件**: `server/application/services/sync_service.py:114`
- **问题**: 嵌套函数内 `completed += 1` 未声明 `nonlocal completed`，Python 视其为局部变量但从未初始化
- **影响**: 运行时抛出 `UnboundLocalError`，批量推送完全无法完成
- **修复**: 在嵌套函数开头添加 `nonlocal completed, failed`

### C6. SSH 私钥文件泄露
- **文件**: `server/application/services/sync_service.py:180-228`
- **问题**: 临时密钥文件在 `try` 块中创建，`os.unlink` 也在 `try` 块中。如果 `exec_command` 抛出异常，跳到 `except` 块，密钥文件不会被清理
- **影响**: 解密后的 SSH 私钥残留在临时目录，凭据泄露
- **修复**: 将 `os.unlink` 移到 `finally` 块

### C7. Shell 注入 — `test_credential_connection`
- **文件**: `server/application/services/script_service.py:172`
- **问题**: `mysql -h {host} -P {port} -u {user} -p'{password}'` 直接拼接，password 中包含单引号可逃逸
- **影响**: 攻击者可通过凭据字段注入任意 Shell 命令
- **修复**: 使用 `shlex.quote()` 转义所有参数，或改用 MySQL 协议直连测试

### C8. Shell 注入 — `sync_files` rsync 命令
- **文件**: `server/application/services/sync_engine_v2.py:92`
- **问题**: `rsync -az --delete {source_path}/ {target_path}/` 直接拼接，路径中可注入 Shell 命令
- **影响**: 攻击者可通过同步路径字段注入任意命令
- **修复**: 使用 `shlex.quote()` 转义路径参数

### C9. WebSocket 消息重复投递
- **文件**: `server/api/websocket.py:294, 312-313`
- **问题**: `broadcast_alert()` 同时调用 `manager.broadcast_local(msg)` 和 `_publish_to_redis(msg)`。本地 Worker 的 Redis 订阅者也收到发布消息并再次调用 `broadcast_local()`。本地客户端收到两次相同告警
- **影响**: 前端显示重复告警，用户体验混乱
- **修复**: 发布 Worker 不直接调 `broadcast_local`，由 Redis 订阅者统一投递；或在消息中加 `sender_worker_id` 过滤自身消息

### C10. Telegram 告警风暴
- **文件**: `server/background/self_monitor.py:36-49`
- **问题**: 每 30 秒检测一次，Redis/MySQL 异常时每次都发 Telegram，无冷却/去重机制。每天 2880 条消息
- **影响**: 告警轰炸导致用户静音或忽略，真正重要告警被淹没
- **修复**: 添加状态转换检测——仅在故障开始和恢复时发送，不在持续故障期间重复发送

### C11. `migrations.py` 导入错误导致启动崩溃
- **文件**: `server/infrastructure/database/migrations.py:12`
- **问题**: `from server.infrastructure.database.session import AsyncSessionLocal, engine`——`session.py` 不存在 `engine` 导出（只有 `get_engine()` 函数和私有 `_engine`）
- **影响**: 应用启动时 `ImportError`，迁移模块完全不可用
- **修复**: 改为 `from server.infrastructure.database.session import AsyncSessionLocal, get_engine`，调用 `engine = get_engine()`

### C12. Jinja2 CVE-2025-27516
- **文件**: `requirements.txt`
- **问题**: Jinja2 3.1.5 存在沙箱逃逸漏洞（CVSS 7.3），已在 3.1.6 修复
- **修复**: 升级到 Jinja2 >= 3.1.6

---

## HIGH 级别问题（18 个）

| # | 文件 | 问题 |
|---|------|------|
| H1 | `sync_engine_v2.py:206-208` | `sync_config` 追加重复 sysctl 条目，无去重 |
| H2 | `sync_engine_v2.py:98` | `duration_seconds` 用 `.seconds` 而非 `.total_seconds()`，超过1天计算错误 |
| H3 | `sync_engine_v2.py:172-173` | 命令同步 `started_at` 在执行后才设置，duration 永远约0 |
| H4 | `heartbeat_flush.py:76-79` | `last_heartbeat` 解析失败静默替换为 `datetime.now()`，**污染** MySQL 数据 |
| H5 | `heartbeat_flush.py:95` | 单次 commit 失败丢失整批 N 台服务器的心跳刷新 |
| H6 | `migrations.py:79-84` | 所有服务器硬编码为 `linux` 平台 ID，category 不匹配的也会指向 linux |
| H7 | `crypto.py:64,69` | `decrypt_value()` 捕获所有异常后静默返回原始密文，无日志 |
| H8 | `telegram/__init__.py:33-34` | `parse_mode=HTML` 但 server_name 等字段未 HTML 转义，特殊字符破坏消息 |
| H9 | `webssh.py:144-202` | SSH 断开时两个 relay 协程静默吞没异常，无日志 |
| H10 | `websocket.py:179-195` | Redis 订阅者网络中断后死循环记录错误，无重连逻辑 |
| H11 | `main.py` | WebSocket `ConnectionManager._heartbeat_task` 不在 shutdown 生命周期中 |
| H12 | `config.py:41-43` | `SECRET_KEY`/`API_KEY`/`ENCRYPTION_KEY` 默认空字符串，JWT 可伪造 |
| H13 | `config.py:124-126` | `load_settings_from_db` 静默吞没所有异常，返回 0（与"0个覆盖"不可区分） |
| H14 | `server_repo.py` | `get_all()` 返回全部 2000+ 行无分页，与文件说明矛盾 |
| H15 | `nginx_https.conf:76-82` | nginx `add_header` 在子 location 中覆盖父级安全头（HSTS 被剥离） |
| H16 | `health_monitor.sh` | `supervisorctl restart` 退出码未检查，重启失败静默忽略 |
| H17 | `script_service.py:131-133` | `ScriptExecution.completed_at` 永远为 NULL，`update_status` 未传递该字段 |
| H18 | `server_service.py:50-95` | `check_all_servers` 和 `push_to_servers` 是存根实现（含 TODO 注释），未实际执行 |

---

## MEDIUM 级别问题（20 个摘要）

1. **WebSocket 无连接限制** — 内存型 DoS 风险
2. **WebSSH `receive_text()` 无消息大小限制** — 可发送超大数据包耗尽内存
3. **`setting_repo.py` SELECT-then-INSERT 竞态条件** — 并发写入可能导致 IntegrityError
4. **`migrations.py` 步骤间无事务原子性** — 部分迁移导致数据不一致
5. **Telegram 网络错误无重试** — 通知永久丢失
6. **CDN 脚本无 SRI 哈希** — Tailwind/Alpine/xterm 被篡改无法检测
7. **全局 401 拦截器缺失** — 前端各页面处理 401 方式不一致
8. **4 个 repo 对不存在记录返回 None** — 调用方无法区分成功/未找到
9. **`health_monitor.sh` 中 `$FAIL_FILE` 竞态条件** — 并发 cron 可能丢计数
10. **Supervisor 缺少 `stopasgroup=true`** — Worker 进程可能残留
11. **`decrypt_value` 在任何异常时返回原始密文** — 调用方无法区分解密成功/失败
12. **`get_db` vs `request.state.db` 双会话模式** — 事务不一致
13. **`auth_jwt.py` 中 JWT 验证异常静默吞没** — 数据库不可达时无日志
14. **`sync_config` 无权限校验** — `sysctl -w` 需要 root，非 root 用户静默失败
15. **`ServerService` 缺少 `audit_repo` 注入** — Server CRUD 零审计日志
16. **Nginx 无速率限制** — `/api/auth/login` 等端点可被暴力攻击
17. **`health_monitor.sh` 缺少 `set -e`** — 命令失败静默继续
18. **`db_backup.sh` 无完整性校验** — 备份文件可能损坏但检测不到
19. **`.gitignore` 缺少 `backups/`、`*.pem`、`.env.*`** — 敏感文件可能被提交
20. **`auth.py` 不处理反向代理 IP** — `request.client.host` 始终是 127.0.0.1

---

## 前端特有问题

| 严重级别 | 文件 | 问题 |
|----------|------|------|
| CRITICAL | `index.html:124` | **存储型 XSS** — `${a.admin_username}` 和 `${a.action}` 未转义注入 innerHTML |
| CRITICAL | `terminal.html:123` | JWT token 放在 WebSocket URL 查询参数中，被 nginx 日志记录 |
| HIGH | `servers.html:45-53` | 4 个服务器数据字段（port/category/agent_version/platform_id）未转义注入 innerHTML |
| HIGH | `settings.html:19` | `${s.key}` 在 innerHTML 和 onclick 属性中未转义 |
| HIGH | `terminal.html:111` | 窗口 resize 时未向服务器发送 RESIZE 消息，CLI 显示错乱 |
| MEDIUM | 全部页面 | 无周期性会话超时检查（token_expires 仅在页面加载时读取一次） |
| MEDIUM | 全部页面 | CDN 脚本无 SRI（Subresource Integrity）哈希 |

---

## 部署特有问题

| 严重级别 | 文件 | 问题 |
|----------|------|------|
| CRITICAL | `deploy/nexus.conf:13` | `user=root` — 应用以 root 身份运行，RCE 即获得完全控制 |
| CRITICAL | 主分支 nginx 配置 | 无 HTTPS，所有流量明文传输 |
| HIGH | `nginx_https.conf` | 缺少 5 个安全头（CSP/X-Frame-Options/X-Content-Type-Options/Referrer-Policy/Permissions-Policy） |
| HIGH | `nginx_https.conf` | 无速率限制（`limit_req_zone`） |
| MEDIUM | `deploy/nexus.conf` | 缺少 `stopasgroup=true` 和 `killasgroup=true` |
| MEDIUM | `db_backup.sh:67` | `MYSQL_PWD` 环境变量在 `/proc` 中可读 |

---

## 审计覆盖范围

| 部门 | 文件数 | CRITICAL | HIGH | MEDIUM | LOW |
|------|--------|----------|------|--------|-----|
| 安全审计（auth/JWT） | 3 | 5 | 4 | 3 | 3 |
| 基础设施审计（DB/Redis/SSH） | 15 | 3 | 5 | 8 | 4 |
| API 路由审计 | 9 | 2 | 4 | 3 | 0 |
| 业务服务审计 | 8 | 5 | 5 | 5 | 3 |
| 实时系统审计 | 8 | 3 | 3 | 5 | 3 |
| 前端审计 | 7 | 3 | 3 | 2 | 2 |
| 部署审计 | 9 | 3 | 4 | 5 | 5 |
| **合计** | **~59** | **12** | **18** | **20** | **15+** |

---

## 建议修复优先级

### 第一批：数据完整性 + 安全漏洞（必须立即修复）
1. **C4** — 修复所有模型时间戳（`default=lambda: datetime.now()`)
2. **C11** — 修复 migrations.py 导入错误
3. **C1** — 给 /logout 添加 JWT 认证
4. **C7/C8** — 修复 Shell 注入（shlex.quote）
5. **C12** — 升级 Jinja2

### 第二批：安全加固
6. **C3** — 删除 SHA-256 密码回退
7. **C2** — 修复审计日志缺少 admin_username/ip_address
8. **C5** — 修复 sync_service UnboundLocalError
9. **C6** — 修复 SSH 私钥文件泄露
10. **H12** — 启动时验证 SECRET_KEY/API_KEY/ENCRYPTION_KEY 非空

### 第三批：功能修复
11. **C9** — 修复 WebSocket 消息重复投递
12. **C10** — 修复 Telegram 告警风暴（添加状态转换检测）
13. **H1-H3** — 修复 sync_engine_v2 各种时间/去重问题
14. **H4-H5** — 修复 heartbeat_flush 数据污染和批处理问题
15. **H7** — 添加 decrypt_value 异常日志

### 第四批：前端 + 部署
16. **前端 XSS** — 所有 innerHTML 中的用户数据必须转义
17. **Nginx 安全头** — 添加 CSP/X-Frame-Options 等
18. **Supervisor** — 改为非 root 用户运行

---

*报告生成：7 部门并行审计，全部基于完整文件逐行阅读，零容忍原则*
