# 本周代码复审报告（2026-05-23 ~ 2026-05-29）

> **用途**：对本周全部改动（含已提交）的一次复审，聚焦后端 C# 安全性与正确性。
> **仓库**：`E:\Work\Core\Simple-FR\Simple`　**分支**：`dev_simpleLite`
> **基线**：`d8d4d3c`（2026-05-21）→ **HEAD**：`1412bf0`（含一次合并远程的 merge）
> **本文档为本地工作记录，默认不纳入版本库。**

---

## 1. 范围与方法

- 本周累计：**37 commit，277 文件，+32461 / −5740 行**；其中后端 C# **96 文件 / +15302 行**为审核核心。
- `MiGu.Server/wwwroot/assets`（占 dirstat 28%）为前端构建产物，忽略。
- 说明：本轮原计划用 5 个并行子代理分模块审核，但因账户账单（unpaid invoice）子代理不可用，改为**人工聚焦后端安全关键面**深度审核；功能性大文件（录制/流/UI/Workspace）与前端做轻量或留待后续。

### 已深度审阅
| 文件 | 关注点 | 结论 |
|------|--------|------|
| `MiGu.Server/Launcher/SimpleLiteLauncher.cs` | 命令注入/进程生命周期/JobObject | 良好 |
| `SimpleLite/Web/AiCompletionService.cs` | SSRF/密钥/Prompt 注入/并发 | 见 RV-01~03 |
| `SimpleLite/PluginManager.cs` | 任意 DLL 加载/RCE/并发 | 良好（入口受限） |
| `SimpleLite/Web/AiConfigController.cs` | 密钥脱敏 | 良好 |
| `SimpleLite/Web/InternalAuthModule.cs` | 8222 鉴权 | 见 RV-04 |
| `SimpleLite/Web/ReflectionApiController.cs`（本会话已改部分） | writableViaApi/只读/Envelope | 已修 ISSUE-05/06/08 |
| `SimpleLite/Persistence/Repositories/CarRepository.cs` | 反序列化白名单 | 已修 ISSUE-10 |

### 鉴权链确认（关键前提）
- `InternalAuthModule`（AR-1/AR-2）统一前置守护所有 `/projection/*`（reflection / map-edit / persistence / ai-config / stream）。
- MiGu.Server 侧 Controller 已加 `[Authorize]`（AR-4）。
- **结论**：AI 生图、插件 reload、字段写入等敏感入口**不可被未认证外部直接触达**——这把多处原本可能的高危项降级为 P2 加固建议。

---

## 2. 发现清单（按严重度）

### P1 — 建议处理（视部署环境）

#### RV-04 — InternalAuthModule loopback 默认无条件放行
- **文件**：`SimpleLite/Web/InternalAuthModule.cs:79-80`（`AllowLoopback` 默认 `true`）
- **现象**：本机回环（127.0.0.1/::1/IPv4-mapped）请求**不校验 token 直接放行**。
- **风险**：单机可信环境无碍；但在**多租户/共享主机**或存在可打到 `127.0.0.1:8222` 的 SSRF 时，本机任意进程即可接管调度内核（execute [MethodMember] / patch 字段 / reload plugin）。
- **建议**：生产（尤其共享主机）通过配置将 `AllowLoopback=false`，强制依赖 `X-Platform-Internal-Token`；并在部署文档标注。代码已预留开关，无需改逻辑，仅需配置 + 文档。

### P2 — 加固建议

#### RV-01 — AiCompletion endpoint 无 SSRF 白名单
- **文件**：`SimpleLite/Web/AiCompletionService.cs:191-197`
- **现象**：`cfg.Endpoint`（管理员经 ai-config 表单可配任意 URL）被服务端 `SharedClient.SendAsync` 直接请求，并携带 `Authorization: Bearer <apiKey>`。
- **风险**：已认证管理员可让服务器向任意（含内网）URL 发带密钥请求 → SSRF / 密钥外泄到非预期端点。
- **建议**：对 `Endpoint` 做 scheme 限制（仅 https）+ host 白名单/黑名单（禁内网段），或显式告警「endpoint 指向内网」。

#### RV-02 — apiKey 明文存盘且 config/ 未被 gitignore
- **文件**：`SimpleLite/Web/AiCompletionService.cs:71-99`（`CWD/config/ai-completion.json` 明文读写）
- **现象**：`apiKey` 明文落盘；`.gitignore` 未覆盖 `config/` 或该文件（已核查无匹配规则）。
- **风险**：误 `git add config/` 或随包分发会泄露 LLM 密钥。
- **建议**：`.gitignore` 增加 `**/config/ai-completion.json`（或运行时 `config/`）；考虑落盘前对 apiKey 做 DPAPI/对称加密。注：GET 已脱敏（`AiConfigController.MaskApiKey`），此项仅针对磁盘文件。

#### RV-03 — AiCompletion 把 LLM 原始响应回抛前端
- **文件**：`SimpleLite/Web/AiCompletionService.cs:199-200`（`throw ... LLM HTTP {status}: {raw}`）
- **现象**：上游非 2xx 时把 `raw` 响应体原样抛出，经 `MapEditApiController` 返回前端。
- **风险**：信息泄露（上游错误细节/内部 endpoint），低概率含敏感片段。
- **建议**：仅记日志保留 `raw`，对前端返回精简错误码与通用提示。

#### RV-05 — 登录阻塞至 Readiness 超时（同 ISSUE-01）
- **文件**：`MiGu.Server/Controllers/AuthController.cs:111` + `SimpleLiteLauncher.WaitForProjectionReady`
- **现状**：已通过 `SimpleLiteOptions.ReadinessTimeoutMs`（可配，0=立即返回）缓解；真正的「fire-and-forget + 前端轮询」留待迭代。

---

## 3. 已确认良好（无需改动）

- **SimpleLiteLauncher**：`displayMode` 由 `NormalizeDisplayMode` 归一为固定枚举（web / web+local），命令行无注入面；`_opts.Arguments` 来自受信 appsettings；进程 `Exited` 事件用 `ReferenceEquals(_proc, proc)` 防误清；JobObject 资源用 `AllocHGlobal/FreeHGlobal` 配对释放。
- **PluginManager**：`reload` 入口（`/reflection/plugins/reload`）只**重扫固定 `./plugins` 目录**，不接受任意路径参数 → 无「任意路径 DLL 加载」漏洞；`Load`/`Unload` 全程加锁（修复历史并发覆盖 bug）；collectible ALC + WeakReference + GC best-effort 卸载。
- **AiConfigController**：GET 脱敏 `apiKey`（`***`+后4位）；POST 对脱敏占位保留旧值，避免被覆盖清空。
- **InternalAuthModule**：token 校验用 `CryptographicOperations.FixedTimeEquals` 防 timing；未配置 token 时一律拒绝远程（仅 loopback 可入）。
- **AiCompletionService 并发**：静态 `SharedClient`（避免 socket exhaustion）+ `Timeout.Infinite` + `CancellationTokenSource` 控制单次超时；`BoundsContext` 用 `AsyncLocal` 跨 await 保持并 `try/finally` 清理。
- **本会话修复**：ISSUE-10（反序列化白名单）/ ISSUE-05（writableViaApi 403）/ ISSUE-06（GET 只读）/ ISSUE-08（Envelope 统一），均经 `dotnet build SimpleLite` 验证 0 错误。

---

## 4. 未深审（建议后续专项）

| 区域 | 文件（本周新增/大改） | 建议关注 |
|------|----------------------|----------|
| 录制/回放 | `SimulationRecorder` `PlaybackController` `RecordingFileIO` `SimulationPlayer` `SchedulerProbe` | 文件句柄/内存映射释放、帧索引边界 |
| 实时流 | `ProjectionStreamModule` `AlarmStreamService` | SSE 连接泄漏/重连/背压 |
| 持久化 | `SceneRepository` `ScriptRepository` `MissionRepository` `PersistenceWorker` `PersistenceBootstrap` | EF DbContext 生命周期、并发、事务 |
| 交互/渲染 | `WorkspaceSceneInput` `CurveControlPointDragService` `SimpleSceneRenderer` `ComposerDockPanel` `SimpleUI` | 线程竞态、事件/定时器泄漏 |
| 前端 | `simple-platform-vue/**` | 鉴权守卫、契约一致、XSS、mock 开关（子代理不可用，本轮未审） |

---

## 5. 结论

本周后端改动建立在会话 53 的安全基线（AR-1~AR-6）之上，**鉴权链完整、未见新增高危漏洞**。建议优先处理 **RV-04（生产关 loopback）** 与 **RV-02（config/ai-completion.json 入 gitignore）** 两项低成本加固；RV-01/RV-03 视风险排期。功能性模块与前端建议在子代理可用后做一轮专项复审。

---

## 6. 本轮处理与补充发现（会话 65 续）

### 6.1 已修复（按「修复 RV-04 + P2 加固项」）
- **RV-01（SSRF）**：`AiCompletionService.GenerateMapInternalAsync` 发请求前校验 endpoint —— 仅允许 http/https（挡 `file://` 等异常 scheme），指向内网/回环时记审计日志但不阻断（兼容自建 LLM）。新增 `IsPrivateOrLoopbackHost`。
- **RV-02（密钥入库）**：`.gitignore` 增 `**/config/ai-completion.json`。
- **RV-03（信息泄露）**：上游 LLM 非 2xx 时原始响应仅进日志（截断 500 字符），前端只返回状态码 + “详见日志”。
- **RV-04（loopback）**：经核实**已可配置**（`PlatformOptions.allowLoopbackBypass`，`ProjectionWebHost.cs:51` 已接线），无需改代码；已在 `MiGu.Server/README.md` 安全须知补充「生产建议 `allowLoopbackBypass=false`」。

### 6.2 补充深审结论（质量良好）
- **实时流** `ProjectionStreamModule` / `AlarmStreamService`：每订阅者一把 `SemaphoreSlim` + `finally` 清理 CTS/锁 + 断连移除；删除车辆时清 `LastAlarmByCar` 防膨胀；反射 `FieldInfo` 缓存。仅 P2（SSE `Dispose` 与 fire-and-forget `Broadcast` 的竞态已被 `catch (ObjectDisposedException)` 吸收；`AlarmStream` Timer Tick 极端慢时可能重入，`ConcurrentDictionary` 保证安全）。
- **录制文件 IO** `RecordingFileIO`：`compressedLen` ≤ 64MB 校验、`ReadExact` 截断检测、GZip 流 `using` 释放、`ScanIndex` 损坏帧停止。仅 P2（解压后大小无上限，GZip 炸弹理论风险；录像为本地受信文件，实际影响小）。

### 6.3 补充发现并修复
- **RV-06（已预防性修复，2026-05-29）** `PersistenceWorker.FlushUpsert`/`FlushDelete` 原在 `ctx.SaveChanges()` / `tx.Commit()` **之前**就调用 `SetHash`/`RemoveHash`，若事务随后回滚，`_lastWrittenHash` 已记录该 hash → 后续 `ShouldSkipWrite` 跳过重写 → 该变更（尤其低频状态如 Mission/Script）可能永久不落库。`AUDIT.md` 的 PB-1~PB-5 未覆盖此项。虽 `Startup.cs:72-74` 已整体下线持久化（worker 不运行），仍按要求预防性修复：
  - `FlushUpsert`/`FlushDelete` 改为把 hash 变更累积到局部 `hashOps`（`hash==null` 表示移除），不再直接改字典；
  - 仅当 `SaveChanges()` + `tx.Commit()` 成功后才由新增的 `ApplyHashOps` 在 `_hashLock` 内原子应用；回滚 / 异常路径不应用，保留「下次仍需写入」语义；
  - 删除不再使用的 `SetHash`/`RemoveHash`。`ReadLints` 验证 0 错误（完整 `dotnet build` 仍受 §6.4 weaver 环境阻塞）。

### 6.4 编译验证说明（受阻于织入工具链环境问题）
- 修复仅触及 `SimpleLite/Web/AiCompletionService.cs`（标准 C#：`Uri.TryCreate` / `IPAddress` / 字符串操作）。
- **首次** `dotnet build SimpleLite/SimpleLite.csproj`（本会话早段）**0 错误**成功，`LessokajiWeaver` 在线织入完整跑过（约 51s，日志 `Online compilation finished`）。
- **本次复验**：SimpleCore 报 20 个 `CS0246/CS0616/CS0641`，全部是 `LessokajiWeaver` 在线织入临时项目（`obj/MaxKeeper_tmp`）应提供的类型缺失（`I18N`/`MethodMember`/`MaxValueKeeper`/`KeepMaxMod`/`ReplaceWithCompileInfo`）；`--no-incremental` 全量重建仍 7s 即失败，未进入在线织入。**错误列表无任何 SimpleLite 文件**。
- 判定：**织入工具链在线服务/状态不可用的环境问题，非本次代码问题**。建议在 VS2022 正常环境、或 `tools\setenv.ps1` 恢复 deps 且 weaver 在线服务可用后复验本次 AI 服务改动。

### 6.5 未完成（建议后续专项）
- `Workspace` / `UI` / `Rendering`、前端全量：本轮因并行子代理不可用（账户账单）未深审，建议子代理恢复后补一轮。

---

## 修订记录
| 日期 | 说明 |
|------|------|
| 2026-05-29 | 初版：会话 65 本周复审（聚焦后端安全关键面；子代理因账单不可用，人工聚焦审核） |
| 2026-05-29 | 续：修复 RV-01/02/03 + RV-04 文档；补深审（流/文件良好、PersistenceWorker 发现 RV-06）；编译复验受阻于 LessokajiWeaver 在线织入环境问题（与改动无关）。 |
| 2026-05-29 | 续：按要求预防性修复 RV-06（hash 延迟到事务 Commit 后由 ApplyHashOps 原子应用）；本会话全部 5 个改动文件 ReadLints 0 错误。 |
