62 lines
5.9 KiB
Markdown
62 lines
5.9 KiB
Markdown
# 审核 / 处置 / 举报(长版明细)
|
||
|
||
## 审核 / 处置 / 举报
|
||
|
||
- 线上通道 = 七牛(`ai.qiniuapi.com/v3/text/censor`,antispam);`[security] text_censor_source = qiniu|aliyun|auto`。
|
||
七牛只硬拦涉政/暴恐/色情,`review` 放行 → 裁决在 `censor/policy.go` + `moderation.Check`。
|
||
- **新增审核调用点必须走 `moderation.Check`**(直调 `censor.Text` 绕过档位)。不可用时 **fail-closed**。
|
||
- ⚠️ 圈内黑话(代称+群体标签+避雷叙述)通用审核拦不住,别指望词库;靠处置 + 举报 + 人工判断。
|
||
- 严格度 lax/standard/strict 存 `users.moderation_strictness`;**受处置用户强制 strict**:
|
||
`moderation.EffectiveStrictness(ctx, userID, raw)`(7 个入口已改)。**查询失败回退用户设置**
|
||
(否则 DB 抖动会让全站突然加严)。处置期内拒绝保存 + 设置页 disabled。
|
||
- ⚠️ **同请求渲染只能用 `ctx.SetError` / `SetInternalError`,不能用 `*Flash`**:flash 写 session,下次请求才读 →
|
||
提示静默丢失。`Set*Flash + Redirect` 才是正常模式。扫描法:找 flash 调用 → 向下第一个 `Success(`/`Redirect(`。
|
||
- 处置四级:L1 停止分发/L2 收窄公开(新回答强制私密、禁 set-public)/L3 冻结发布(仍可收信)/
|
||
L4 整页停用(`/restricted`)。到期靠 `expires_at`,**无 cron**;`restrict` 是**替换语义**;比较用 `RestrictionAtLeast`。
|
||
- ⚠️ L2 强制私密必须在**审核通过之后**,且与写回答**同一条 UPDATE**(`AnswerByIDPrivate`):
|
||
拆开会留下「公开+已作答」窗口,第二条失败还会变成「私密+无回答」。
|
||
- ⚠️ **`ListAPI`(`/api/v1/user/{domain}/questions`)不经过 Pager 中间件**,L4 与页面密码必须自己校验。
|
||
- ⚠️ **整箱分享卡 `ShareCard` 与单题分享卡都要拦**,且拦截必须在缓存查询之前(否则 30 分钟内仍给旧图)。
|
||
- 举报 `POST /_/{domain}/{qid}/report`:免登录 + 验证码 + 限流;同 IP 同题唯一索引 →
|
||
**幂等且提示与首次一致**(否则成了内容存在性探测器)。箱主只看「被举报 N 次」,**不给 IP**。
|
||
- 闭环:删除/设私密/`hide-public` → `MarkAcceptedByQuestion`;待处理超 30 天视为过期。
|
||
⚠️ `hide-public` 里必须**先快照目标 ID 再更新**(更新后带 `is_private=false` 的子查询恒为空集)。
|
||
- 告警:24h≥8 或 7d≥20 → 邮件到 `[security] security_alert_email`(24h 冷却);单条恰好 3 人举报 → 提醒箱主。
|
||
⚠️ 该邮箱为空时静默跳过、无日志;改配置必须重启。
|
||
- `/rules` 是处置依据页(四语言);注册页必须勾 `agree_rules`(服务端同样校验),改 e2e 脚本要同步。
|
||
- `restrict`/`unrestrict` 默认给箱主发通知(按 `users.language`,`mail/restriction-notice.html`),
|
||
**只含级别名/到期/规则链接,不含 `--reason`**;`--no-notify` 跳过。`--days > 90` 要 `--force`。
|
||
⚠️ **到期自动解除不发信**(读表比较时间)。
|
||
|
||
## 蜜罐(2026-10-21)
|
||
|
||
- 落点是**改写 `f.NotFound`**(不是新中间件):NotFound 只在路由表落空时触发 → 与业务路由零交集;
|
||
位于全局 Use 链之后(body 已限 12 MiB);组级 CSRF 不作用于它。
|
||
- flamego 短路机制:`context.run()` 在 `ResponseWriter().Written()` 为真时 return → 写完响应不调 `ctx.Next()` 即止。
|
||
- 蜜罐命中的路径由蜜罐自己 `autoban.Record`,autoban 中间件须跳过(否则一次请求计两遍,阈值减半)。
|
||
- `HoneypotHit` 不嵌 `dbutil.Model`(审计证据只追加,不软删)。
|
||
- 假页只含 canary + 假信息;错误提示不区分「用户不存在」与「密码错误」(防用户名枚举)。
|
||
- 密码 CLI 默认脱敏;告警邮件含明文凭据 → 收件人只能是运维本人。
|
||
|
||
### 蜜罐封禁与 enable_auto_ban 解耦(2026-10-21 线上实测修复)
|
||
- ⚠️ **`Record()` 第一行 `if !b.enabled { return }` 会让 `enable_auto_ban=false` 连坐蜜罐** →
|
||
蜜罐只落库不封禁(实测命中 70+ 次零封禁)。已修:新增 `RecordHoneypot()` 独立通道 + 独立阈值/时长。
|
||
- `IsBanned()` 不得受 `enabled` 约束:`enabled` 只控制「是否主动创建封禁」,不控制「是否尊重已有封禁」。
|
||
- `RecordHoneypot` 的键必须与 `Record` 一致(同样 `shared.LimitKey` 归一化),否则 `IsBanned` 判不出来。
|
||
- 备份/配置/密钥泄露扫描(`/backup.zip`、`/db.sql`、`/id_rsa.pem`)文件名主体随机,**只有后缀稳定**
|
||
→ 用 `path.Base()` 后缀匹配表,不能只靠子串(`"/.zip"` 匹配不到 `/backup.zip`)。
|
||
|
||
### 蜜罐表体积与自动清理(2026-10-21)
|
||
- `honeypot_hits` 是**只追加**审计表,写入完全由外部扫描流量驱动(不可预期、不可节流)
|
||
→ 必须有自动保留期清理,否则表体积只增不减。
|
||
- 两道闸:时间(`honeypot_retain_days`)+ 数量(`honeypot_max_rows`)。数量闸不可省 ——
|
||
攻击者可用海量源 IP 在保留期内把表撑爆。
|
||
- ⚠️ PG 的 `DELETE` 只标记死元组,**表文件不会变小**;无 VACUUM 时表体积单调膨胀
|
||
(实测 22→52 MB)。清理后必须 `VACUUM (ANALYZE)`,但注意 **VACUUM 不缩小文件、
|
||
只让空间可复用**(止住增长即可,不必 `VACUUM FULL`)。
|
||
- ⚠️ `VACUUM` 不能在事务块/函数体内执行 → GORM 走 `Exec()`(Raw 处理器,不包事务)可行。
|
||
- ⚠️ 清理器的 `runOnce` 必须自己再挡一次 `retainDays<=0`,否则「保留 0 天」= 全删光。
|
||
- 索引:GORM 去掉 struct tag 的 `index` **不会**删既有库上的旧索引 → 需显式 DropIndex。
|
||
删索引前先用 EXPLAIN 确认零查询使用(`provider` 索引实测 10 万行 648 kB 且零使用)。
|
||
- CLI `honeypot size` 报告行数/磁盘占用/时间跨度 —— 把「有没有臃肿」交给运维自查。
|