Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ jobs:
pip install pyyaml
python scripts/check_declared_capabilities.py

- name: Pin cross-constraint gate (锁集自洽)
# 锁文件内部必须互相满足:A==x 声明 requires B<2,而锁里 B==2.3 就是破图 ——
# 用户 `pip install -r requirements-lock.txt` 装出来的是坏环境,`pip check` 才报。
# 这类问题以前只有 pre-commit 的 `files:` 触发(改动 lock 才跑),所以已经坏在
# main 上的锁集没人看得见(2026-09-21 实测:transformers==4.52.1 要 tokenizers<0.22
# 而锁里是 0.23.2,另有 antlr4/mpmath 两处)。这里每次 PR 都跑,漂移当场红。
run: |
pip install packaging
python scripts/check_pin_crossconflicts.py

- name: Integrity manifest sync gate (security)
# 防止「核心模块代码已改、完整性清单未重新生成」:改动 app/integrated_app/ 下
# 任一 _CORE_MODULES 文件后若未同步 security/integrity_manifest.json,运行时
Expand Down
7 changes: 5 additions & 2 deletions app/integrated_app/engines/indextts2_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,8 +501,11 @@ def _load_model(self) -> None:
IndexTTS2 = _infer_mod.IndexTTS2
except ImportError as e:
raise ImportError(
f"indextts 未安装或缺少 {self._infer_module} 推理模块。"
"PyPI 无 'indextts' 包,请从官方仓库安装:\n"
f"indextts 未安装或缺少 {self._infer_module} 推理模块"
f"(底层错误:{type(e).__name__}: {e})。"
"若 indextts 已安装,多半是 transformers 版本不匹配——本引擎按 "
"transformers 4.52.1 验证通过,4.57.x 下该模块导入会失败。"
"确未安装时(PyPI 无 'indextts' 包)从官方仓库装:\n"
" git clone https://github.com/index-tts/index-tts.git\n"
" cd index-tts && pip install -e .\n"
"(2.0 与 2.5 共用同一仓库,安装一次即同时提供 infer_v2 与 infer_v2_5)"
Expand Down
37 changes: 30 additions & 7 deletions docs/DOD.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,11 @@
第 5 项的"按钮要做事"已真机验过:400 错误块带 `role="alert"` + 「立即加载」「重试」两个按钮,
点重试 XHR 计数 +1 且错误块重新渲染(此前该按钮调的是从未定义的函数,纯死键,#133),
第 9 项 6 轮切换实测空闲显存回到切换前水平(spread 416MB,无单调递增)。
**仍需人工:第 7 项真开一次屏幕阅读器。**(第 8 项断网首屏已于 2026-09-20 转为机器判定,见 §5.2)
**人工项已全部走完**:第 7 项屏幕阅读器由仓库所有者于 **2026-09-21 实机判定通过**
(NVDA/VoiceOver 走"选音色→输入→生成→播放",遮罩阶段变化有朗读);第 8 项断网首屏已于
2026-09-20 转为机器判定(`scripts/check_offline_first_paint.py`,见 §5.2)。
- 2026-09-21 本轮补记(分发产物与前端竞态,`fix/tab-race-and-dist-payload`,基线 = `origin/main` e104809):
* **门禁**:非 e2e **2078 passed / 35 skipped / 0 failed,2m24s,覆盖率 52.04%**;
* **门禁**:非 e2e **2078 passed / 35 skipped / 0 failed,2m33s,覆盖率 52.11%**;
`tests/e2e`(服务在线)**68 passed / 5 skipped**;mypy **103 = 基线**;
ruff check 全通过、`ruff format --check` 359 文件已格式化;完整性清单 **16/16 一致**;
无硬编码路径 exit 0;`test_portable_bundle.ps1` **49 条断言全通过**;
Expand All @@ -113,11 +115,32 @@
并新增 `tests/test_packaging_manifest.py`(25 条断言,含 4 条变异自证)常驻把关(#134)。
* **新修的用户可见缺陷**:侧栏换页竞态 —— 注入 1.5s 慢响应后,**修前 6/6 组末态被先点那一页
占住**(用户在错的页面上点生成 → 400),带 `hx-sync="#tab-content:queue last"` 后 **0/6**(#136)。
* **仍未覆盖**:`docker build` 本机 daemon 未起(CI 的 `docker-smoke.yml` 会真构建,镜像结论
目前是 `.dockerignore` 静态复演);桌面安装包链路(staging → data 7z → NSIS)**无任何 workflow
调用**、本机也无从安装(`scripts/installer/` 只有一个 4.3 MB `Setup.exe`、无同目录分卷),
所以 `unpack_desktop.ps1` 新加的许可/字体落地核对只过了语法层;htmx 1.9.10 在队列换页时
自抛的一次 `insertBefore` TypeError(末态正确)未清,需另案升级 vendored 库。
* **依赖锁集自洽性**(比告警更要紧):两份 lock 当时自相矛盾 —— `tokenizers==0.23.2` 同时违反
`transformers 4.52.1` 要的 `<0.22` 与 `indextts` 要的 `==0.21.0`;`antlr4-python3-runtime` 4.13.2
违反 hydra/omegaconf 的 `==4.9.*`;`mpmath` 1.4.1 违反 sympy 的 `<1.4`。后果不是 CI 红,而是
**按 lock 装环境的人拿到破图**,开发机能跑只是因为比 lock 早(本机 30/73 条与 lock 不一致)。
三行改到合法且与已验证环境一致后 `check_pin_crossconflicts.py` 从 **exit 1 → exit 0**(95 包
冲突 0、未核验 0),并接进 `ci.yml` 每次 PR 跑 —— 它原先只挂在 pre-commit 的 `files:` 条件上,
已经坏在 main 上的锁集它永远看不见。
* **transformers 上界被实测钉死**:4.57.6 下 VoxCPM2 正常(243,164 B / RMS 5360)但
**IndexTTS 2.5 / 2.0 双双 `infer_v2_5` / `infer_v2` 导入失败**;回到 4.52.1 + tokenizers 0.21.0
后三引擎真推理全通(2.5:214,040 B / RMS 6176;2.0:205,124 B / RMS 6926;VoxCPM2:230,148 B /
RMS 4615;每次卸载显存回到 ~3.5 GB)。故 `pyproject`/`requirements.txt` 里那句
`transformers>=4.57.0`(9-14 搭在一条只讲 gpu-smoke 的提交里进来的)站不住,但改回 4.52.x 会让
pip-audit 与 Trivy 两道 CI 安全门禁同时变红(扫到 4.52.4 的"4.53 已修"CVE)——**下界与引擎可用性
互斥**,本轮只落地无争议部分(锁集合法化 + 检查器接进 CI + 报错文案),下界原样保留并在
`pyproject.toml` 里写清两条出路,岔口交所有者;
引擎加载失败时的报错也不再断言"PyPI 无 indextts 包",改为带上底层 ImportError 与版本不匹配提示。
20 条 Dependabot 告警因此**没有一条能靠现在就升级消掉**,分诊见 `docs/SECURITY_DEPENDABOT_TRIAGE.md`。
* **已知缺口**:CI 冒烟 `scripts/gpu_smoke_minimal.py` 只覆盖 voxcpm2 + indextts2(走 OpenAI 口,
而 `tts-1` / `tts-1-hd` 两个模型名里没有 IndexTTS **2.0** 的位置);2.0 的真推理今天人工验过,
要接进冒烟需改用 `/api/generate/indextts2` 形态并在 GPU runner 上复验,另案。
* **仍未覆盖**:桌面安装包链路(staging → data 7z → NSIS)**无任何 workflow 调用**、本机也无从安装
(`scripts/installer/` 只有一个 4.3 MB `Setup.exe`、无同目录分卷),所以 `unpack_desktop.ps1`
新加的许可/字体落地核对只过了语法层,`release_gate.ps1` 的第 ⑥ 步也只在发版/dispatch 时跑;
htmx 1.9.10 在队列换页时自抛的一次 `insertBefore` TypeError(末态正确)未清,需另案升级 vendored 库。
(原先记的"`docker build` 未实跑"已消掉:PR #100 的 `Boot hardened container & probe` 里,
新加的"镜像内许可文本 + 字体计数"步骤在真构建产物上结论 success。)

## 6. 安全 & 隐私

Expand Down
102 changes: 102 additions & 0 deletions docs/SECURITY_DEPENDABOT_TRIAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# 依赖漏洞告警分诊(Dependabot alerts triage)

日期:2026-09-21 适用:`main` @ PR #100 之后 责任人:仓库所有者
状态:**20 条 open 告警 = 10 个公告 × 2 份清单**(`requirements-lock.txt`、
`launcher/requirements-small.txt` 各计一次)。本文只谈"能不能修 / 该不该修 / 谁挡住"。

## 1. 结论速览

| # | CVE | 包 | 严重度 | 修复版本 | 谁挡住它 | 本仓可达性 | 建议处置 |
|---|---|---|---|---|---|---|---|
| A1 | CVE-2025-5197 | transformers | medium | 4.53.0 | `indextts==…`钉 `transformers==4.52.1` | tokenizer 正则 ReDoS,且仅 Marian/部分 tokenizer;我们走 GPT2/CLIP 路径,输入是本机用户自己键入的文本 | 关闭理由:本机自用 + 代码路径不涉及;待引擎适配后随版本升级自然消除 |
| A2 | CVE-2025-6051 | transformers | medium | 4.53.0 | 同上 | 同 A1 | 同 A1 |
| A3 | CVE-2025-6638 | transformers | medium | 4.53.0 | 同上 | MarianTokenizer —— 本仓不加载 Marian 模型 | 同 A1 |
| A4 | CVE-2025-6921 | transformers | medium | 4.53.0 | 同上 | AdaLight tokenizer 正则 —— 未使用 | 同 A1 |
| A5 | CVE-2026-1839 | transformers | medium | 5.0.0rc3 | 引擎钉 4.52.1(且 5.x 是大版本破坏性升级) | `Trainer` 代码执行:**全仓无 HF `Trainer` 用法**(`training/trainer.py` 里的 `LoRATrainer` 是我们自己的类,`grep from transformers import Trainer` 零命中) | 关闭理由:代码路径不存在 |
| A6 | CVE-2026-4372 | transformers | high | 5.3.0 | 同上 | 远程代码执行类:需要加载不可信仓库且 `trust_remote_code=True`。本仓权重是本地目录 + 人工核验 + SHA-256(`LOCAL_RULES.md` 禁区流程);唯一从 Hub 取物的是 `security/content_safety.py` 的 CLIP `AutoTokenizer.from_pretrained(model_name, revision=…)`(固定 revision、非攻击者可控) | 关闭理由:不接受用户指定模型源;保留复点 |
| A7 | CVE-2026-5241 | transformers | high | 5.5.0 | 同上 | LightGlue 等模型初始化路径 —— 本仓不加载该类模型 | 关闭理由:未使用组件 |
| A8 | CVE-2026-9856 | transformers | high | 5.10.0 | 同上 | `save_pretrained` 经 chat template 造成任意文件写:**全仓无 `save_pretrained` 调用**(音色保存写的是我们自己的目录) | 关闭理由:代码路径不存在 |
| P1 | CVE-2025-4565 | protobuf | high | 4.25.8 | `descript-audiotools 0.7.2` 要 `>=3.9.2,<3.20`;`modelscope` 的 audio extra 要 `>=3.19.0,<3.21.0` | protobuf 在本仓只被 tensorboard / tensorboardX / modelscope / descript-audiotools 间接使用,服务端**不解析任何来自网络的 protobuf/JSON wire 数据**(`grep google.protobuf app/` 零命中) | 关闭理由:无攻击面;要真修得换掉 descript-audiotools/modelscope 的锁上界 |
| P2 | CVE-2026-0994 | protobuf | high | 5.29.6 | 同上(且跨两个大版本) | 同 P1 | 同 P1 |

一句话:**10 个公告里没有一个能靠"升个版"现在就消掉** —— 4 个 ReDoS 被 IndexTTS 引擎的精确 pin 挡住,
4 个需要 transformers 5.x(大版本),2 个需要 protobuf 越过 `descript-audiotools<3.20` /
`modelscope<3.21` 两道上游上界。

## 2. 本次实测证据(为什么明知有告警还是不升)

```
transformers 4.57.6 + tokenizers 0.22.2(满足当时声明的 >=4.57.0):
VoxCPM2 加载 200 → 合成 RIFF、243,164 B、RMS 5360 ← 正常
IndexTTS 2.5 加载失败:ImportError: indextts 缺少 indextts.infer_v2_5 推理模块
IndexTTS 2.0 加载失败:ImportError: indextts 缺少 indextts.infer_v2 推理模块

transformers 4.52.1 + tokenizers 0.21.0(引擎元数据要求的组合,最终留在树里的状态):
IndexTTS 2.5 加载 200 → 回取 RIFF 214,040 B、RMS 6176(约 2.2 s 音频)
IndexTTS 2.0 加载 200 → 回取 RIFF 205,124 B、RMS 6926
VoxCPM2 加载 200 → 回取 RIFF 230,148 B、RMS 4615
三次卸载后显存回到 3.5 GB 基线(RTX 5070 Ti 12227 MiB,加载前空闲 8.2~8.4 GB)
```

即"升到 4.57 就能顺手关掉 4 条 ReDoS"这条路,代价是**产品两个引擎直接不可用**。

**这里有一个必须所有者拍的岔口**(PR #101 实测撞上的):把声明下界改回 4.52.x 会让
`transformers 4.52.4` 进入解析结果,于是 CI 的两道安全门禁同时变红 ——
`Dependency Vulnerability Scan (pip-audit)`(PYSEC-2025-216、PYSEC-2026-198/228/229/392)与
`docker-build.yml` 的 Trivy 扫描步骤(镜像构建本身成功)。反过来,留着 `>=4.57.0` 门禁是绿的,
但任何人按 `pyproject`/`requirements.txt` 装环境,IndexTTS 两个引擎都起不来。
也就是说**当前 main 的"安全门禁绿"是踩在引擎跑不起来的版本声明上的**。

两条出路,本 PR 只做了无争议的那半(锁集合法性 + 检查器接线),下界原样留着并加注释指向本文:

1. 下界改回 `>=4.52.1,<4.53`(运行时正确),同时给 pip-audit / Trivy 加**逐条带理由**的豁免
(理由引用 §1 表的可达性判定与 §4 的复点条件);
2. 或者先让 IndexTTS 侧适配 transformers ≥4.53(上游放开精确 pin,或我们改 vendored 拷贝),
再自然抬下界、告警一并消掉。

> 复现这套对比时的坑(已记 GOTCHAS #137):本服务的端口被占时会**自动顺延到下一个端口**,
> 而沙箱里"停掉后台命令"只杀外层 shell、不杀 `python` 子进程。结果是新起的干净服务落在 7870,
> 脚本按 7869 打到的却是**上一代还在跑的旧进程**,把"已恢复环境"误报成"仍然失败"。
> 判据:跑之前先 `netstat -ano | grep LISTENING | grep :786`,确认监听只有一个且 PID 是新的。

## 3. 顺带修掉的、比告警更要紧的一件事

`requirements-lock.txt` / `launcher/requirements-small.txt` 当时**自相矛盾**(与告警无关的独立缺陷):

| 锁里的值 | 违反谁的要求 | 已改为 |
|---|---|---|
| `tokenizers==0.23.2` | `transformers 4.52.1` 要 `>=0.21,<0.22`;`indextts` 要 `==0.21.0` | `0.21.0` |
| `antlr4-python3-runtime==4.13.2` | `hydra-core 1.3.x` / `omegaconf 2.3.x` 要 `==4.9.*` | `4.9.3` |
| `mpmath==1.4.1` | `sympy 1.14.0` 要 `<1.4` | `1.3.0` |

后果不是"CI 红",而是**任何人按 lock 装环境都装出一个 `pip check` 报错的破图**,而开发机能跑
是因为它比 lock 早(本机实装 30/73 条与 lock 不一致)。之所以一直没人发现:
`scripts/check_pin_crossconflicts.py`(PR #98 引入)**只挂在 pre-commit 的 `files:` 条件上**
—— 只有改动 lock 才触发,已经坏在 main 上的锁集它永远不会去看。本次把它接进
`.github/workflows/ci.yml` 的 lint job,每次 PR 与 push 都跑;改完检查器输出:
`校验 PyPI 版本 95 个,冲突 0,未核验 0 → PASS`。

## 4. 复点条件(满足其一就重新评估对应告警)

1. IndexTTS 官方放开 `transformers` 精确 pin → 立刻可关 A1–A4(4.53+),届时再判 5.x 的 A5–A8。
2. `descript-audiotools` / `modelscope` 放开 `protobuf<3.20 / <3.21` → 可关 P1–P2。
3. 若本服务将来**监听非本机地址**或**接受用户指定模型名/HF 仓库**,A6/A8/P1/P2 的
"无攻击面"前提立即失效,必须重判。当前证据:`config.yaml host: "127.0.0.1"`、
`run_server(ip="127.0.0.1")`。
4. 若 `vllm_backend.py`(现在无人引用,其 `trust_remote_code` 默认 True)被接进入口,A6 立即升为必修。

## 5. 需要仓库所有者点头的动作

GitHub 安全页的 20 条 dismiss 属**共享状态写操作**,我没有代做,也没有在这份文档里写死
`dismissed_reason` 的取值 —— 那个枚举我该现查而不是照记忆写。执行前先取真值:

```bash
# 看某条告警现在的状态与字段名(读操作,不改状态)
gh api repos/ReSerendipity/TTS_MultiModel/dependabot/alerts/22 \
-q '{state, dismiss_reason, advisory: .security_advisory.cve_id}'
```

拿到合法枚举后,按 §1 表里"建议处置"一列逐条 PATCH(`state=dismissed` + 对应的
`dismissed_reason` + 注释里引用本文对应行号),**先跑 1 条确认语义再批量**。
每条注释必须自带绑定理由(哪条代码路径不存在 / 被哪个上游上界挡住 / 复点条件),
不接受"误报"三个字了事。
6 changes: 3 additions & 3 deletions launcher/requirements-small.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ aliyun-python-sdk-core==2.16.1
aliyun-python-sdk-kms==2.16.5
annotated-doc==0.0.5
annotated-types==0.8.0
antlr4-python3-runtime==4.13.2
antlr4-python3-runtime==4.9.3
anyio==4.15.1
certifi==2026.7.22
cffi==2.1.1
Expand Down Expand Up @@ -50,7 +50,7 @@ markupsafe==3.0.3
mdurl==0.1.2
modelscope==1.40.1
modelscope-hub==0.4.3
mpmath==1.4.1
mpmath==1.3.0
msgpack==1.2.2
narwhals==2.26.0
networkx==3.6.1
Expand Down Expand Up @@ -90,7 +90,7 @@ starlette==1.6.0
sympy==1.14.0
threadpoolctl==3.7.0
tiktoken==0.14.0
tokenizers==0.23.2
tokenizers==0.21.0
torch-complex==0.4.4
tqdm==4.70.1
transformers==4.52.1
Expand Down
10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,15 @@ dependencies = [
"torch>=2.5.1",
"torchvision>=0.20.1",
"torchaudio>=2.5.1",
# 版本策略:VoxCPM2 / IndexTTS2 的 tokenizer 与 modeling 需要较新 transformers API。
# ⚠ 这条下界与引擎可用性互斥,取舍交仓库所有者,别顺手改(证据见
# docs/SECURITY_DEPENDABOT_TRIAGE.md §2):2026-09-21 实测 4.57.6 下 VoxCPM2 正常
# (合成 243,164 B / RMS 5360)但 IndexTTS 2.5 与 2.0 的 infer_v2_5 / infer_v2 直接
# ImportError;4.52.1 + tokenizers 0.21.0 下三引擎真推理全通(2.5:214,040 B / RMS 6176;
# 2.0:205,124 B / RMS 6926)。但把下界改回 4.52.x 会让 pip-audit 与 Trivy 两道 CI
# 安全门禁同时变红(扫到 4.52.4 的"4.53 已修"CVE),所以这里暂留 >=4.57:
# 门禁绿,代价是按本声明装环境的人两个引擎起不来。出路二选一——
# 1) 下界改回 >=4.52.1,<4.53,并给两道门禁加逐条带理由的豁免;
# 2) 先让 IndexTTS 侧适配 transformers >=4.53(上游放开精确 pin 或改 vendored 拷贝)。
"transformers>=4.57.0",
"tokenizers>=0.19.0",
# === AI 生态 ===
Expand Down
6 changes: 3 additions & 3 deletions requirements-lock.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ aliyun-python-sdk-core==2.16.1
aliyun-python-sdk-kms==2.16.5
annotated-doc==0.0.5
annotated-types==0.8.0
antlr4-python3-runtime==4.13.2
antlr4-python3-runtime==4.9.3
anyio==4.15.1
certifi==2026.7.22
cffi==2.1.1
Expand Down Expand Up @@ -45,7 +45,7 @@ markupsafe==3.0.3
mdurl==0.1.2
modelscope==1.40.1
modelscope-hub==0.4.3
mpmath==1.4.1
mpmath==1.3.0
msgpack==1.2.2
narwhals==2.26.0
networkx==3.6.1
Expand Down Expand Up @@ -85,7 +85,7 @@ starlette==1.6.0
sympy==1.14.0
threadpoolctl==3.7.0
tiktoken==0.14.0
tokenizers==0.23.2
tokenizers==0.21.0
torch==2.14.0
torch-complex==0.4.4
torchaudio==2.11.0
Expand Down
Loading