提问链路上 ActiveLevel 有 7 处调用,投票链路 0 处。于是 L4(整页停用)处置某人后: /_/domain 已经 302 到 /restricted,但 /vote/{token} 照常渲染、照常收票;被处置者 还能发新投票;探索页的「热门/全部投票」也不做处置过滤 —— 同一张页面里 「箱主消失了、他的投票还在曝光」。这是运营口径不一致,不是越权。 补齐三处,与提问链路对齐: - vote.Page L4 → 302 /restricted(同箱主页;查询失败 fail-open,与 Pager 一致) - vote.Submit L4 → 拒绝(页面进不去不代表直接构造 POST 也拦得住) - VotePublish L2 起拒绝(L2 已是「收窄公开」,不该再新增公开内容) - 探索页投票列表改用 Restrictions.ActiveLevels 批量过滤(与 GetShowInHome 同口径, 一次查询避免 N+1),功能开关过滤逻辑保持不变 新增字典 key vote.err.restrictedPublish(四语言)。 验证(本地实例): - L4 后 /vote/{token} 302 → /restricted;撤销后恢复 200 - 探索页该投票出现次数 0(处置中)→ 2(撤销后) - 草稿投票在 L2 下发布后状态停留 draft,撤销后同一请求变为 published
P1: - 外链图片:markdown 渲染层只放行站内与自有 CDN 图片。配置里的 image_bucket_cdn_host 常写成 host/path,统一归一化后再比对,避免把自家图一起剥掉 - 密码:页面/提问密码最小 6 位;密码比对挪到验证码之后;页面密码失败锁定 5 次/10 分钟 - 客户端 IP:CF-Connecting-IP / EO-Client-IP 只在 TCP 对端可信(回环或 trusted_proxies) 时采信,公网对端带来的权威头一律忽略 P2/P3: - 跳转参数收敛到 shared.SafeRedirectTarget:拒反斜杠与控制字符(url.Parse 会把 %0d%0a 解成 CRLF,伪造 Location),最后再用 path.Clean 验一次前缀 - 注册不再暴露「邮箱已注册 / 已停用」:与全新注册共用同一个成功出口,只在日志留痕 - CSP:默认 enforce,白名单由配置派生,覆盖 umami(运行时注入)、Turnstile 与 按 sentry_dsn 条件放行的 Sentry CDN - /user 与 /_ 下发 no-store;补 X-XSS-Protection、Permissions-Policy,并把安全头前移到 静态资源中间件之前(原先静态文件拿不到 nosniff/HSTS) - 封禁事实落库 ban_records;429 统一带 Retry-After 与本地化文案 - token 比较改常量时间;探索页页码上限;JSON 响应显式 Content-Type 全局复查补的两处: - 私聊 SendMessage 的 POST 缺 conv.OwnerID 校验,被拉黑者可换 domain 继续发消息 - 页面密码 next 与登录 to 同源的反斜杠外跳 验收:verify_p1_fixes 4/4、verify_round2 19/19、verify_page_next 6/6、 verify_chat_owner 3/3(含变异对照);单测与 go build ./... 全通过。
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.