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

7.0 KiB
Raw Blame History

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 确认问题。

二、逐项复审证据

状态:已修复。

  • 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 字符。复审过程中确认原字段:

VoterToken *string `gorm:"size:64;..."`

不足以存储完整 token,可能导致 MySQL 严格模式下匿名提交失败或 token 截断。现已改为:

VoterToken *string `gorm:"size:128;uniqueIndex:idx_submission_token"`

并加入 token 长度回归断言。部署时依赖启动阶段的 GORM AutoMigrate 将字段扩容;上线前应在预生产数据库确认迁移结果和唯一索引状态。

四、验证结果

通过

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
# 无输出,检查通过

全量验证限制

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 构建环境,并配置测试数据库后执行:

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