Files
tamabox/outputs/TamaBox-v4-code-review-followup-2026-08-26.md
T
tamakyi e3e59e7c69 chore: baseline snapshot before moderation-strictness feature
Repo .git was damaged (refs/ missing, index truncated, object store near-empty).
Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak,
then full worktree committed as a rollback baseline.
2026-09-10 10:56:27 +08:00

141 lines
7.0 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.
# TamaBox-v4 修复后代码复审报告
- **复审时间**:2026-08-26
- **审阅对象**:TamaBox-v4
- **复审基线**:`outputs/TamaBox-v4-code-review-report-2026-08-26.md`
- **复审范围**:上一轮 4 项确认问题的修复调用链、相关数据库模型、模板与回归测试;并对修复影响面做定向静态复查。
- **结论口径**:本报告仅将能由当前代码直接确认的问题列为确认问题;环境限制与建议性事项单独列出。
## 一、复审结论
上一轮报告中的 4 项确认问题均已完成代码级修复,未发现原问题仍可沿原路径复现:
| 原问题 | 复审结论 | 关键位置 |
|---|---|---|
| P1-01 匿名 `vote_fp` Cookie 可伪造 | **已修复** | `route/vote/vote.go:62-120, 691-700` |
| P1-02 Markdown 实体编码导致 XSS | **已修复** | `internal/template/markdown.go:143-147` |
| P2-01 多选重复选项可重复计票 | **已修复** | `route/vote/vote.go:232-247, 749-775, 802-809` |
| P2-02 投票次数配置与一人一票约束矛盾 | **已修复** | `route/user/vote.go:199-202, 563-566`、`templates/user/vote-form.html:85-88` |
本轮复审另发现并修复了一个由 P1-01 修复引入的兼容性问题:签名 token 长度为 108 字符,原数据库字段 `size:64` 不足,已调整为 `size:128`(`internal/db/vote_submissions.go:54-55`)。
**当前代码级结论:未发现 P0/P1/P2 确认问题。**
## 二、逐项复审证据
### 1. P1-01:匿名指纹 Cookie 伪造与防刷绕过
**状态:已修复。**
- `newVoterToken` 生成 32 字节随机 nonce,格式为 `64 位十六进制 nonce + "." + HMAC-SHA256 签名`。
- `signVoterToken` 使用 `conf.Server.XSRFKey` 作为服务端密钥;配置初始化已强制要求该密钥非空。
- `validVoterToken` 检查分段数量、nonce 长度、十六进制格式,并使用 `hmac.Equal` 比较签名。
- `ensureVoterToken` 对无效/伪造 Cookie 重新签发;`currentVoterToken` 对无效 Cookie 返回空值,不再将客户端任意字符串作为身份依据。
- 匿名提交无论是否带有效 Cookie,均按投票维度执行 24 小时 IP 计数;当前一人一票策略下,同一投票同一 IP 窗口内最多接受一次匿名提交。
- 数据库 `UNIQUE(vote_id, voter_token)` 仍作为有效 token 的最终并发防重兜底。
**残余设计取舍**:IP 兜底会对 NAT、校园网、公司网络等共享出口产生误伤;这是保守防刷策略,不是身份伪造漏洞。若业务必须支持共享出口多人投票,应改用验证码、设备/行为风控或更细粒度限流,而不能取消服务端限流。
### 2. P1-02:Markdown 实体编码 XSS
**状态:已修复。**
- `sanitizeHTML` 的 `html.TextToken` 分支现在对 tokenizer 已解码的文本调用 `html.EscapeString` 后再写入 `template.HTML`。
- 因此 `<script>`、双重编码输入不会在重建 HTML 时重新变成可执行标签。
- 标签、属性、URL 白名单逻辑保持有效;事件属性和危险协议仍被过滤。
- 回归测试已覆盖实体编码、双重编码、script、事件属性、危险 URL、iframe、SVG、style 以及 font color 属性注入。
### 3. P2-01:多选重复 option ID 重复计票
**状态:已修复。**
- `parseMultipleOptionIDs` 统一负责多选参数解析。
- 解析时使用 `seen` 集合;同一 `option_id` 出现两次直接返回“选项不能重复选择”。
- 同时校验空提交、最大选项数、选项归属和 `uint` 溢出,避免异常数值绕过校验。
- `LoadResults` 的多选聚合层也对历史数据中的重复 option ID 做防御性去重,因此旧异常数据不会在每次结果计算中再次放大统计。
- 新增测试覆盖正常多选、重复选项、超出上限、无效选项和整数溢出。
### 4. P2-02:MaxVotesPerFingerprint 与数据库约束矛盾
**状态:已修复。**
- 当前数据模型明确采用一人一票:登录用户由 `UNIQUE(vote_id, user_id)` 约束,匿名用户由 `UNIQUE(vote_id, voter_token)` 与 IP 兜底共同保护。
- 管理端解析不再信任旧页面或手工请求提交的 `max_votes_per_fingerprint`,统一按 `1` 保存。
- 新建/编辑失败回填也固定显示 `1`;表单输入设置为 `readonly`、`max="1"`,避免继续传达可配置多票语义。
- 四种语言的字段文案已改为明确的一人一票含义。
## 三、本轮新增兼容性问题及修复
签名 token 的组成长度为:64 字符 nonce + 1 个分隔符 + 43 字符 Raw Base64URL HMAC = **108 字符**。复审过程中确认原字段:
```go
VoterToken *string `gorm:"size:64;..."`
```
不足以存储完整 token,可能导致 MySQL 严格模式下匿名提交失败或 token 截断。现已改为:
```go
VoterToken *string `gorm:"size:128;uniqueIndex:idx_submission_token"`
```
并加入 token 长度回归断言。部署时依赖启动阶段的 GORM AutoMigrate 将字段扩容;上线前应在预生产数据库确认迁移结果和唯一索引状态。
## 四、验证结果
### 通过
```text
gofmt -d route/vote/vote.go route/vote/vote_stats_test.go route/user/vote.go \
internal/db/vote_submissions.go
# 无输出,格式通过
go test -count=1 ./route/vote ./internal/template ./templates ./internal/i18n \
./internal/security/autoban ./internal/security/censor ./internal/imagecheck \
./internal/shared ./internal/cache
# 全部 PASS
go vet ./route/vote ./internal/template ./templates ./internal/i18n
# 无输出,检查通过
```
### 全量验证限制
```text
go test ./...
go vet ./...
```
未能全绿,原因是当前环境已有的基础设施限制,不是本轮修复代码引入:
1. `github.com/chai2010/[email protected]` 在当前 Windows 环境缺少 CGO/WebP 编译符号(如 `webpGetInfo`、`webpDecodeRGB`),导致 `internal/storage`、`route/question`、`route/user`、主程序等相关包无法完整编译。
2. `internal/db` 测试未配置数据库类型,测试初始化报 `Unknown database type: ""`。
因此仍需在生产同构的 Linux/CGO 构建环境,并配置测试数据库后执行:
```text
go test ./...
go vet ./...
govulncheck ./...
```
## 五、上线前建议
1. 在预生产数据库执行/确认 AutoMigrate,确认 `vote_submissions.voter_token` 已扩容到至少 128 字符,且唯一索引仍存在。
2. 使用两个不同匿名客户端验证:同一 IP 同一投票第二次提交被拦截;伪造 `vote_fp` 会被覆盖且不能绕过 IP 限制;正常匿名首次投票成功。
3. 用真实浏览器验证实体编码文本仅以文字显示,不执行脚本;覆盖提问、回答、投票描述和结果页等 Markdown 输出点。
4. 若站点存在大量共享出口,结合验证码或行为风控评估 IP 一票策略的误伤率。
## 六、修改文件清单
- `internal/template/markdown.go`
- `internal/template/markdown_test.go`
- `route/vote/vote.go`
- `route/vote/vote_stats_test.go`
- `route/user/vote.go`
- `templates/user/vote-form.html`
- `internal/db/vote_submissions.go`
- `internal/i18n/dict_zh_cn.go`
- `internal/i18n/dict_zh_tw.go`
- `internal/i18n/dict_en.go`
- `internal/i18n/dict_ja.go`