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.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
# P1 修复落地记录(P1-3 / 4 / 5 / 7 / 8 / 9 / 10)
|
||||
|
||||
- **日期**:2026-08-31
|
||||
- **范围**:按用户要求顺序实施 7 个 P1 项(P1-6 密码比较不在本次范围;P1-3 按要求**不使用 Turnstile/recaptcha**)。
|
||||
- **P0-1(IP 伪造)暂缓**,按用户指示留待后续。
|
||||
|
||||
## 1. P1-3 验证码限流 + 常量时间比较
|
||||
**文件**:`internal/captcha/math.go`、`internal/route/route.go`
|
||||
- 答案比对改为 `crypto/subtle.ConstantTimeCompare`,消除时序侧信道。
|
||||
- `VerifyOnly`(模态框预校验)累加尝试次数,达到 `maxCaptchaAttempts=5` 即销毁该验证码,防止无限轮询爆破。
|
||||
- `GenerateMath` 处理 `rand.Int` 错误(不再忽略)。
|
||||
- `/api/captcha/generate`、`/api/captcha/verify` 加上 `rateLimitMiddleware(20, 1m)`。
|
||||
- ⚠️ 这是**缓解**而非根治:数学答案仍返回给客户端,脚本理论上可读题。根治依赖 P0-1(IP 信任校验)落地后整体提权。
|
||||
|
||||
## 2. P1-4 追问"等主人回复"服务端校验
|
||||
**文件**:`internal/db/follow_ups.go`、`route/question/follow_up.go`
|
||||
- 新增 `FollowUps.GetLatestByQuestionID`(`Order(created_at DESC) Limit 1`)。
|
||||
- `PublishFollowUp` 在计数校验后,取该问题最新一条追问;若其 `Answer == ""` 直接拒绝(不再仅依赖前端 `waitingFollowUpReply` 隐藏表单)。
|
||||
|
||||
## 3. P1-5 审核可信域名精确匹配
|
||||
**文件**:`internal/security/censor/text.go`
|
||||
- `RemoveTrustedURL` 改为边界感知:仅当可信域名后紧跟 `/`、`?`、`#` 或位于字符串结尾时才剥离,避免 `https://github.com.evil.tld` 被误判为 `github.com` 子域而漏审。
|
||||
|
||||
## 4. P1-7 删除回退功能配额计数
|
||||
**文件**:`internal/db/feature_control.go`、`internal/db/questions.go`、`internal/db/conversation.go`
|
||||
- `IncrementQuota` 负 delta 增加 `WHERE quota_count > 0` 守卫,防止计数减到负数。
|
||||
- `questions.DeleteByID` 删除提问后回退 `FeatureQuestion` 配额 -1。
|
||||
- `conversations.MarkDeletedByOwner` 删除私聊会话后回退 `FeatureChat` 配额 -1(并先取 OwnerID,已删除/不存在时幂等返回)。
|
||||
|
||||
## 5. P1-8 自动封禁仅计真正 404
|
||||
**文件**:`internal/route/autoban.go`、`internal/route/route.go`
|
||||
- `autobanMiddleware` 改为接收共享 `*autoban.Autoban` 实例(由 `New()` 创建一次)。
|
||||
- 真正的 404 探测计数移入 `f.NotFound` 处理器单独记录;不再把业务性 `302 → /`(语言切换 / 登出 / 投票不存在 / 黑名单跳转)误判为路径扫描导致误封。
|
||||
|
||||
## 6. P1-9 服务反代路径穿越 / query / 超时
|
||||
**文件**:`route/service/proxy.go`
|
||||
- `forwardPath` 经 `path.Clean` 归一化,`singleJoiningSlash` 安全拼接,杜绝 `../` 越出白名单前缀。
|
||||
- 透传原始 `RawQuery`(此前静默丢弃)。
|
||||
- 复用带超时的共享 `Transport`(`ResponseHeaderTimeout=15s`、`IdleConnTimeout=30s`),不再每请求新建。
|
||||
|
||||
## 7. P1-10 像素追踪路由收紧
|
||||
**文件**:`internal/route/route.go`、`internal/context/context.go`、`route/pixel/pixel.go`
|
||||
- `/api/v1/pixel/{**}` 由 `f.Any` 改为 `f.Get` + `rateLimitMiddleware(30, 1m)`,仅放行只读 GET,并补限流。
|
||||
- `context.go` 去掉对 `/api/v1/pixel/` 的 CSRF 豁免(GET 本不需 CSRF,写方法已被路由层禁止)。
|
||||
- `pixel.go`:路径 `path.Clean` 后必须仍在 `/api/` 之下,否则拒绝;响应头仅透传白名单(Content-Type/Length/Encoding/Disposition/Cache-Control/ETag/Expires/Last-Modified/Pragma/Vary),**不透传 Set-Cookie 等**。
|
||||
|
||||
## 验证状态
|
||||
- ✅ `go build` / `go vet` 全绿(不依赖 storage 的 5 个包):`internal/captcha`、`internal/security/censor`、`internal/db`、`route/pixel`、`route/service`。
|
||||
- ✅ 单元测试通过:`internal/security/censor`、`internal/security/autoban`。
|
||||
- ✅ 12 个改动文件 `gofmt -w` 已格式化。
|
||||
- ⚠️ `internal/route/route.go`、`internal/route/autoban.go`、`internal/context/context.go`、`route/question/follow_up.go` 因传递依赖 `internal/storage`(`github.com/chai2010/webp` 缺 libwebp,Windows 沙箱 CGO 限制)**无法在此环境完整编译**;已用 `gofmt -e` 确认无语法错误,请在 Linux / 带 libwebp 环境做完整 `go build ./...` 验证。
|
||||
Reference in New Issue
Block a user