# UtiliVera Scan 0.5.1 安装器独立审核（Round 1）

审核日期：2026-08-20  
审核角色：独立安装/发布审核员（未参与构建）  
审核范围：冻结 EXE、Inno Setup、181-file payload、10 语言静默/负向 .NET 4.8、10/10 构建稳定性、许可、卸载残留、升级、签名及公开发布边界  
审核方式：只读；未修改产品、安装器、证据或网站产物

## 结论

**内部安装工程：FAIL（P0=0、内部 P1=1）。正式公开下载：BLOCKED（另有外部 P1=2）。P2=5。**

安装器的冻结身份、181/181 payload、静默安装/repair/卸载、10 种安装语言、10 种 .NET 4.8 负向门禁和 10/10 确定性构建均有一致证据，主体安装机制已达到专业候选水平。但是，第三方许可清单没有覆盖实际随包分发的大量原生依赖，因此不能批准内部安装工程为 PASS。

此外，安装器与主 EXE 均未签名，生产 `/scan/` 和 `/scan/releases/0.5.1/` 均为 404。可以先部署不包含二进制、Offer 或下载链接的预发布网站，但当前候选不得正式公开下载，30 天 1,000 有效下载计时不得启动。

## 1. 冻结候选身份：PASS

本轮直接读取冻结文件并独立计算：

| 对象 | 大小 | SHA-256 | Authenticode |
|---|---:|---|---|
| `UtiliVeraScan.exe` | 347,136 bytes | `EA12A7B0D89091907255E3FFC36F2638066EC4BCA33FAC7508E8EA8DAB7B1011` | `NotSigned` |
| `UtiliVeraScan-Setup-0.5.1-Windows-x64.exe` | 93,986,490 bytes | `7124955D56DEC0EA8112B4D6225B4336E7AE4F93BC8D40313F9BADA7914EEC32` | `NotSigned` |

主 EXE FileVersion/ProductVersion 均为 `0.5.1.0`，产品名为 UtiliVera Scan；安装器 `SHA256SUMS.txt` 与上述 installer SHA 一致。build/test/stability 三份 evidence 都引用同一候选身份并诚实记录 `BLOCKED_NOT_SIGNED`，未发现旧候选混入。

## 2. 181-file payload：PASS

本审核员没有接受 build JSON 的自报结论，而是以当前冻结 `dist` 为基准逐项重算：

- manifest entries：181
- 当前 payload files（排除 `dist/installer`）：181
- missing：0
- extra：0
- bytes mismatch：0
- SHA-256 mismatch：0
- manifest frozen EXE SHA：`EA12A7...B1011`

`build-installer.ps1` 还会在编译前锁定 EXE SHA/FileVersion，并在安装后逐项比对 181 个文件的 bytes/SHA；当前 build evidence 的 `embeddedManifestVerified=true`、`validationFailuresBeforePass=0` 与原始结果一致。

## 3. Inno Setup 与 .NET 4.8 门禁：PASS

Inno 源码核验结果：

- 固定 `AppId={{8A446267-5E62-4412-87A8-56D7E0F1EC74}`，支持同产品覆盖/升级识别；
- `PrivilegesRequired=lowest`，可由 dialog/command line 覆盖，默认走当前用户安装；
- `ArchitecturesAllowed=x64compatible`、Windows 最低版本 10.0.17763；
- .NET 4.8 检查同时读取 HKLM 64/32 位 `Release >= 528040`；
- `InitializeSetup` 在文件复制前执行；
- 10 个 `DotNet48Required` 本地化键均存在且各出现一次；
- 简/繁中文 Inno 语言文件使用固定 SHA 下载，当前本地 SHA 与脚本 pin 完全一致。

`/SIMULATENODOTNET` 只用于把门禁稳定切到失败分支，不会绕过生产门禁。10 份负向原始日志均出现 `InitializeSetup returned False; aborting`，随后 EAbort/deinitialize；均无创建安装目录、写文件或安装成功信号。

## 4. 10 语言静默矩阵：PASS

`installer-test-evidence-0.5.1.json` 记录 10/10：

- `/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /LANG=...` install exit 0；
- 安装后主 EXE 存在；
- silent uninstall exit 0；
- 每个语言测试目录最终移除；
- 对应的 .NET 4.8 负向测试全部 exit 1，`payloadCopied=false`。

本轮抽查原始语言日志，命令行实际包含对应 `/LANG=` 值；负向日志 10/10 都在 `InitializeSetup` 阶段停止。23 份持久日志（3 份主 install/repair/uninstall、10 份语言安装、10 份负向门禁）均存在且非空，总计 1,139,207 bytes。

## 5. 静默安装、repair、卸载与残留：PASS（既有范围）

同一冻结候选的原始日志和脚本断言支持：

- 首次静默安装 exit 0；
- installed EXE SHA 等于冻结 EXE SHA；
- 安装后的 OCR language enumeration 成功；
- 同版 repair exit 0，EXE SHA 未漂移；
- 静默卸载 exit 0；
- 安装目录、当前用户开始菜单快捷方式和 HKCU/HKLM uninstall registration 均无残留；
- uninstall 原始日志以 `Uninstallation process succeeded`、`Removed all? Yes` 结束。

`%LOCALAPPDATA%\UtiliVera\Scan` 下的设置与 profile 被有意保留，用户创建的扫描件、PDF 和 `.uvscan` 工程不会删除。这属于防止数据丢失的正确策略，不应把用户数据当成程序残留；但需要用户可见披露，见 P2。

## 6. 10/10 确定性构建：PASS

稳定性 evidence 逐行核验：

- requested/passed：10/10
- result rows：10
- non-zero exit：0
- unique hashes：1
- deterministic：true
- 唯一 installer SHA：`7124955D56DEC0EA8112B4D6225B4336E7AE4F93BC8D40313F9BADA7914EEC32`

每轮构建同时执行 181-file 安装后验证，不是仅编译文件并比较哈希。证据没有“失败后重试才通过”的掩盖：最终 build evidence 为 `validationFailuresBeforePass=0`。

## 7. 内部 P1：第三方许可覆盖不完整——OPEN

### 证据

冻结 manifest 在 `assets/tesseract`、`assets/qpdf` 和 `assets/color` 下包含 50 项资源，其中 45 项是 DLL/EXE/traineddata/TTF/ICC；仅 DLL 就有 31 个（29 个唯一名称）。实际随包分发的依赖包括但不限于：

- Leptonica、OpenSSL `libcrypto`、libarchive、expat；
- libjpeg、libpng、libtiff、libwebp、OpenJPEG、GIF、JBIG、Lerc；
- zlib、zstd、lz4、lzma、bzip2、libdeflate、libb2；
- libthai、libiconv；
- GCC/libstdc++/winpthreads 运行库；
- sRGB ICC profile 和 OCR PDF 字体。

当前 `THIRD-PARTY-NOTICES.md` 只有 Fluent UI System Icons、Application icon、Tesseract OCR/tessdata 和 qpdf 四个标题，没有列出上述依赖的组件版本、版权方、许可证或相应 notice 路径。现有 MIT、Tesseract Apache-2.0、traineddata Apache-2.0 和 qpdf notice 不能证明其余每个实际分发组件已经完成许可义务。

这不是文案美化问题，而是二进制再分发合规证据缺失。当前 `thirdPartyNoticesInstalled=true` 只能证明文件被安装，不能证明内容完整。

### 可测验收标准

1. 从 181-file manifest 建立完整第三方 SBOM/许可清单，对每个非 UtiliVera 二进制、模型、字体、ICC profile 和图标来源映射：组件名、版本、来源 URL、版权方、许可证标识、所需 notice/license 文件；
2. 补齐每项许可证要求的完整文本、版权声明和归属声明，尤其核清所有 Tesseract/qpdf 运行时 DLL、MinGW runtime、OpenSSL、Leptonica 及图像/压缩库；
3. 将最终 `THIRD-PARTY-NOTICES.md` 与所有必需 license 文件纳入 181-file payload（文件数变化后重新冻结为新 manifest），并确保安装后可离线查看；
4. 自动测试安装后的 SBOM 每一行都能解析到存在且非空的 notice/license 文件，未映射第三方资源数必须为 0；
5. 重建候选，重新计算 installer/EXE/package hashes，并由同一独立审核员逐项复核后关闭。

## 8. 外部正式发布 P1

### 外部 P1-1：安装器与主 EXE 未签名——OPEN

本轮独立实测两者均为 `NotSigned`，违反仓库 `AGENTS.md` 的 public build code-signing 硬门禁。

**可测验收标准：**使用受信证书签署主 EXE 和最终 Inno 包并添加 RFC 3161 时间戳；在干净 Windows 10/11 验证两者 Authenticode 均为 `Valid`；签名会改变文件字节，必须重新冻结 bytes/SHA、181-file manifest、10/10 build 和完整安装矩阵。只有最后一次独立审核通过的签名包可公开。

### 外部 P1-2：生产产品/版本 URL 未部署——OPEN

Inno 固定以下 URL：

- `https://utilivera.com/scan/`
- `https://utilivera.com/scan/releases/0.5.1/`

本轮独立 HTTPS GET 两者均返回 404；`https://utilivera.com/support/` 返回 200。产品与版本 URL 尚不能作为安装后的有效支持/更新落点。

**可测验收标准：**生产产品页与版本页均 HTTPS 200，准确显示最终签名 installer/EXE 的版本、bytes、SHA、系统要求和发布状态；安装后验证 Publisher/Support/Updates 链接。签名前可以部署无二进制预发布页，但页面不得包含当前未签名包、`SoftwareApplication.Offer`、`downloadUrl` 或可用下载按钮。

## 9. 非阻断 P2

1. **跨版本升级不足：**固定 AppId 和 `UsePreviousAppDir=yes` 具备升级基础，但只有同版 repair，没有真实 0.5.0→0.5.1、降级拒绝/回滚或用户设置迁移矩阵。
2. **权限范围矩阵不足：**已覆盖默认 lowest/current-user 路径；尚未分别验证管理员 override 后的 Program Files、HKLM、all-users 快捷方式所有权与卸载清理。
3. **桌面快捷方式未覆盖：**`/TASKS=desktopicon` 是公开可选项，但没有自动验证创建、目标、工作目录与卸载清理。
4. **用户数据保留披露不足：**保留策略只在内部 test evidence 清楚表述，最终用户 README/卸载体验没有明确列出 `%LOCALAPPDATA%\UtiliVera\Scan` 及手动清理方法。
5. **语言卸载原始日志不完整：**10 种语言都记录了安装日志和卸载 exit/目录断言，但没有为每种语言持久保存独立 uninstall log；应补充以增强可追溯性。

## 10. 门禁摘要

| 门禁 | Round 1 判定 |
|---|---|
| 冻结 installer/EXE bytes、SHA、版本 | PASS |
| 181/181 payload 独立复算 | PASS（0 mismatch） |
| .NET 4.8 真实门禁 | PASS |
| 10 种本地化门禁键 | PASS |
| 10 语言静默 install/uninstall | PASS |
| 10 语言负向门禁 | PASS（均 exit 1，复制前阻断） |
| 23 份持久日志 | PASS |
| 同版 repair 与既有卸载残留 | PASS |
| 10/10 deterministic build | PASS（唯一 SHA） |
| 第三方许可/SBOM 完整性 | **内部 P1，FAIL** |
| 产品/release 生产 URL | **外部 P1，404** |
| installer 与主 EXE 签名 | **外部 P1，NotSigned** |
| 无二进制预发布网站 | 可部署，但不是正式发布 |
| 正式公开下载 | **BLOCKED** |

## 最终判定

**UtiliVera Scan 0.5.1 安装器 Round 1：FAIL。P0=0，内部 P1=1，外部 P1=2，P2=5。**

主体安装机制已通过 181-file、10 语言、.NET 4.8、静默安装/repair/卸载和 10/10 构建稳定性验证；但第三方许可覆盖不完整，内部安装工程尚不能 PASS。即使该内部 P1 修复，正式公开下载仍必须同时满足受信代码签名和生产 URL 部署门槛。当前 `712495...EEC32` 未签名安装器与 `EA12A7...B1011` 未签名主 EXE 均不得公开，30 天下载目标计时不得启动。
