Files
tamabox/outputs/TamaBox-v4-remediation-2026-08-27.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

5.7 KiB
Raw Blame History

TamaBox-v4 评审修复落地报告(2026-08-27)

基于 outputs/TamaBox-v4-code-review-2026-08-27.md 的 P2/P3 findings,落实 4 项修复。 改动遵循「最小、可读、可回滚」原则;每项均给出根因、改法、验证状态。


已落地(4 项)

P3-1 autoban 404 探测失效 ✅

文件:internal/route/autoban.go 根因:autobanMiddleware 内 defer 在该 handler 返回时触发,而 Flamego run() 会在 handler 返回后自动推进链路,因此 defer 执行时下游路由尚未运行,rw.Status() 恒为 0 → KindNotFound 分支永远进不去。敏感路径封禁因 kind 在 body 内同步置位,不受影响。 修复:在 defer 之后、函数返回前加 ctx.Next()。经核对 [email protected]:Next() 即 c.index++; c.run(),会把下游全部跑完并让 c.index 越过末尾,外层循环随后只「超调」一次即退出,不会重复执行下游。 验证:internal/security/autoban 单测通过(安全核心逻辑);gofmt 语法校验通过。

P3-4 草稿/归档提交提示误导 ✅

文件:route/vote/vote.go(Submit,约 647 行) 根因:!isVoteActive 仅二分「已结束 / 尚未开始」。草稿(draft)与归档(archived)状态均 Status != published 且通常无 EndAt,双双落入「尚未开始」,而重定向目标 /vote/{LinkToken} 本身会拦截草稿/归档 → 死循环式跳转。 修复:按 vote.Status 精确分支:

  • draft → 「该投票尚未发布」
  • archived → 「该投票已归档,无法参与」
  • 其余:isVoteEnded → 「该投票已结束」/ 否则「该投票尚未开始」 验证:gofmt 语法校验通过;状态常量 db.VoteStatusDraft/Archived 已核实存在。

P2-2 匿名投票强制验证码 ✅

方向(用户拍板):匿名投票始终要求验证码(或行为验证码)。 文件:route/vote/vote.go(Submit)、route/user/vote.go(parseVoteMeta / renderVoteFormWithInput)、templates/user/vote-form.html 根因:原逻辑 if vote.RequireCaptcha 仅在创建者开启验证码时才校验匿名提交;关闭后匿名用户清除 vote_fp Cookie 即可换签名 token 重复投票,仅剩 limit*10 的 IP 兜底。 修复:

  1. Submit 匿名分支门槛由 if vote.RequireCaptcha 改为 if conf.Recaptcha.CaptchaType != ""——只要全局配置了验证码,匿名提交一律校验;未配置(无密钥部署)时不阻断,避免整崩。
  2. parseVoteMeta(草稿创建/编辑、发布中编辑三路共用的唯一 require_captcha 读取点)与 renderVoteFormWithInput 强制 requireCaptcha := true,使存储值与策略一致。
  3. 模板复选框置为 checked disabled,对 owner 明确「不可关闭」。 对齐既有决策:db.Votes.RequireCaptcha gorm 默认 true,votes_test.go:63 断言「匿名验证码默认开启(决策 #3)」,i18n 即「匿名投票需通过验证码」。本修复使该决策不可被 UI 关闭。 验证:gofmt 语法校验通过;conf.Recaptcha.CaptchaType 字段在 internal/context/context.go:269 已使用,类型无误。

P2-1 结果缓存写穿失效 ✅(务实版)

文件:internal/cache/cache.go、route/vote/vote.go(Submit) 根因:LoadResults 对 multiple/rating/rank/form 类型需 ListByVoteID 全量加载并在 Go 层逐条 json.Unmarshal 聚合(O(N));resultsCache(30s TTL)仅在过期时重算,新提交后结果页最多陈旧 30s,且每次窗口到期都要重算一次全量。 修复:

  1. 给 TTLCache 新增 Delete(key) 方法。
  2. Submit 成功 Create 提交后调用 resultsCache.Delete(strconv.FormatUint(vote.ID,10))——写穿式失效:新投票后立即清除该投票结果缓存,结果页随即反映,且把 O(N) 重算成本收敛到「每次新投票一次」。 未做(需独立迁移):真正的 O(1) SQL 下推受限于选项选择结果存于 JSON FormData 大字段(跨 MySQL/PG 做 JSON 聚合既脆弱又依赖具体版本)。彻底消除 O(N) 需把选择项正态化到独立表(如 vote_submission_selections(vote_id, submission_id, option_id)),以 GROUP BY option_id 聚合。建议作为后续独立 schema 迁移评估,不在本次改动内。 验证:internal/cache 单测通过(含新 Delete);gofmt 语法校验通过。

本次未改(留作独立决策)

  • P3-3 死配置 MaxVotesPerFingerprint:全链路写死为 1、提交侧从不读取,属历史遗留。鉴于设计本意即「一人一票」,建议整体移除该字段与表单接线;涉及 DB schema 迁移,需你确认后再做。
  • P3-2 进程内缓存跨实例陈旧:TTLCache 为进程本地,多实例下清缓存不跨实例。可接受最终一致 / 缩短 TTL / 迁 Redis——架构决策,不急。

构建与验证状态

  • ✅ 语法:gofmt -e -l 对 4 个改动的 .go 文件均无报错。
  • ✅ 单元:go test ./internal/security/autoban/... ./internal/cache/... 全绿。
  • ⚠️ 全量构建受阻(既有环境问题,非本次改动):github.com/chai2010/webp 依赖 libwebp 本机 C 库(CGO),当前沙箱缺该库,报 undefined: webpGetInfo 等。该依赖经传递链被 route/vote、route/user、internal/route 引入,故这几个包在本环境无法完整 go build。在带 libwebp 的 CI/Docker 环境中可正常构建。建议你在本地/CI 跑一次全量 go build ./... 与 go test ./... 做最终确认。

改动文件清单

  • internal/route/autoban.go
  • internal/cache/cache.go
  • route/vote/vote.go
  • route/user/vote.go
  • templates/user/vote-form.html