docs(memory): 记录第二轮复查结论与六项修复的验证方式
- 新增 topics/quality.md:业务正确性 / 运维 / 前端体验四类结论,含 「私聊取最早 50 条」「Classic() 的 Logger 记 RequestURI」这两个坑的成因, 以及处置覆盖面、Redis 两种故障模式、产品缺口(无 page view / 提问者找不回自己的提问) - 2026-10-29.md:本轮六项修复的改动与验证结果,另记两个实现坑 (limit 兜底不该抬大用户配置;Python bytes 字面量不支持 \uXXXX) - topics/sec.md / 2026-10-27.md:补上一轮遗漏的字体自动发现与 CSP enforce 结论 - MEMORY.md:索引加 quality,处置覆盖面一条标注投票链路已补齐
This commit is contained in:
@@ -65,3 +65,28 @@
|
|||||||
box-dev-api,没部署修复所以 4/4 FAIL;② 页面密码入口限流 10 次/分钟 + p1-2c 的 10 分钟锁定
|
box-dev-api,没部署修复所以 4/4 FAIL;② 页面密码入口限流 10 次/分钟 + p1-2c 的 10 分钟锁定
|
||||||
会让 `verify_page_next.py` 后两项 429。顺手把该脚本判据从「Location 不含 evil」改成精确比对
|
会让 `verify_page_next.py` 后两项 429。顺手把该脚本判据从「Location 不含 evil」改成精确比对
|
||||||
`/_/<domain>`、每条载荷开新会话、429 单独报错。四套最终:4/4、19/19、6/6、3/3。库已清回只剩 `tb-local`。
|
`/_/<domain>`、每条载荷开新会话、429 单独报错。四套最终:4/4、19/19、6/6、3/3。库已清回只剩 `tb-local`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第三轮:用户「按最优解决定」→ CSP 切 enforce + 字体落地 + 提交
|
||||||
|
|
||||||
|
**CSP 默认改 enforce**(`cspMode()` 空值 = enforce;写错值仍退 report)。依据不是猜:
|
||||||
|
写了 `outputs/sec-audit-2026-10-25/csp_sweep.py` + `csp_sweep.cjs`(Playwright + 本机 Chrome,
|
||||||
|
监听 `securitypolicyviolation`),report 模式先测一遍、enforce 再测一遍,**18 页面 + 2 图片端点零违规**。
|
||||||
|
巡检器自己做了变异自检(页面里主动 `fetch("http://csp-selftest.example/x")` → 确实报 connect-src),
|
||||||
|
否则「0 违规」可能只是监听器没挂上。
|
||||||
|
**补的漏项**:`browser.sentry-cdn.com` —— header.html 里 Sentry 在 `{{ if ne SentryDSN "" }}` 里,
|
||||||
|
本地没配 DSN 所以巡检碰不到,线上一开就会静默失效;现在按 `conf.App.SentryDSN` 条件放行 + 单测双向断言。
|
||||||
|
|
||||||
|
**分享卡字体**:补 glyf 系统候选(wqy-zenhei / wqy-microhei / DroidSansFallback / uming)并压到
|
||||||
|
Noto(CFF) 之前;`static/fonts` 同时按 CWD 和可执行文件目录找;`CheckShareCardFont()` 在 runWeb 里
|
||||||
|
启动即探(命中 Info 带路径,缺失 WARN 给 apt 命令)。实测 `apt install fonts-wqy-zenhei` 后
|
||||||
|
零配置即 `font=…wqy-zenhei.ttc`,`/share-card` 与单题分享卡从 500 → `200 image/jpeg`,
|
||||||
|
中文/二维码正常(图存 `outputs/sec-audit-2026-10-25/sharecard-*.jpg`)。
|
||||||
|
|
||||||
|
**提交**:三个 commit(7802bb2 安全修复 / e807dea 字体 / 430c6ef 记忆)。**push 失败**:
|
||||||
|
`git.shiroko.one` 的 https 存取凭据被拒(`Failed to authenticate user`),SSH 路(`id_ed25519_wsl`)
|
||||||
|
也未被该仓库授权 —— 需要用户更新 token 后自己 push。
|
||||||
|
|
||||||
|
**全量单测**:带 `DB_TYPE=postgres DB_USER=tamabox DB_PASSWORD=… DB_HOST=127.0.0.1` 全绿;
|
||||||
|
不带 DB 变量时一批 DB 门控用例会报 `Unknown database type: ""`(不是回归)。
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# 2026-10-29
|
||||||
|
|
||||||
|
## 第二轮综合复查(非安全向)
|
||||||
|
|
||||||
|
用户要求「再确认还存在什么问题与风险点」+「从用户角度有什么值得改进和增加的功能」。
|
||||||
|
前一轮是安全专项(P1/P2/P3 全修完),这轮换到**业务正确性 / 运维 / 前端体验 / 产品能力**四个面。
|
||||||
|
|
||||||
|
产出:`outputs/review-2026-10-29.md`(分级清单 + 复现方式 + 产品建议)。
|
||||||
|
|
||||||
|
### 实测复现的两条 P0
|
||||||
|
|
||||||
|
1. **私聊 50 条截断**:`GetByConversationID` 是 `Order("created_at ASC") + Limit(50)`,
|
||||||
|
唯一调用方永远传 nil cursor;会话上限 200 → 第 51 条起**写库成功但永久不可见**。
|
||||||
|
本地造 55 条实测:页面只渲染 #1–#50。这不是性能问题,是「数据在库里但用户看不到」。
|
||||||
|
2. **访问日志泄露 token**:`flamego.Classic()` = `Use(Logger(), Recovery(), Static())`,
|
||||||
|
Logger 记 `RequestURI`(含 query)。实测日志出现 `path=/_/tblocal/4?t=SECRETTOKEN123`。
|
||||||
|
`?t=` 是私密题钥匙、`?code=` 是改密凭证。**tracing 中间件早就为同一风险避开了 RequestURI
|
||||||
|
并写了注释,框架默认 Logger 漏了。**
|
||||||
|
|
||||||
|
### 代码确认的 P1
|
||||||
|
|
||||||
|
- **无优雅关闭**:全仓 `signal.Notify`/`Shutdown` 零命中,SIGTERM 直接杀进程 → 每次重启掐断在途 POST。
|
||||||
|
- **提问入库后图片失败不回滚**(`page.go:660` → `:680`),叠加 DuplicateGuard 5 分钟 → 用户以为没发出、5 分钟内不能重提。
|
||||||
|
- **Redis 两种故障**:session 在 Redis 上宕机即全站 500(flamego session load 直接 panic);
|
||||||
|
`cache.Cacher` **没有** `if conf.Redis.Addr != ""`,空 Addr 被 go-redis 回退到 localhost:6379 → 没配 Redis 时找回密码静默坏掉。
|
||||||
|
- **处置不覆盖投票**:提问链路 7 处 `ActiveLevel`,投票链路 0 处。L4 处置后 `/_/domain` 已 302 到 /restricted,
|
||||||
|
但 `/vote/{token}` 照常可投、可发新投票、探索页照常曝光。
|
||||||
|
|
||||||
|
### 产品缺口(重要,别忘)
|
||||||
|
|
||||||
|
- **完全没有访问量数据**:`SiteAnalytics` 是全站聚合,无 per-user 维度,也没有 page view 表。
|
||||||
|
箱主无法知道自己被看了多少次。
|
||||||
|
- **提问者找不回自己的提问**:不填邮箱时唯一手段是成功页那条链接,关页即失联;全仓 `localStorage` 零命中。
|
||||||
|
→ 建议前端用 localStorage 记 `{domain, id, token}`,箱主页显示「你有 N 条提问已被回答」。
|
||||||
|
- `NotifyType` 只有 `email` / `none`,无 Telegram / Webhook。
|
||||||
|
- 模板零 `application/ld+json`(SEO 富摘要缺失),无 RSS。
|
||||||
|
- 图片原图直传(无前端压缩),配合 ReadTimeout 30s → 手机端 8MB 图必超时。
|
||||||
|
|
||||||
|
### 顺带确认的「不用动」
|
||||||
|
|
||||||
|
四语言字典 917 条完全对齐(`TestDictionaryComplete` PASS)、深色模式正文对比度 4.8–6.8:1 达标、
|
||||||
|
gzip 实现质量高、后台移动端短标签方案做得细。
|
||||||
|
|
||||||
|
### 已修(同日,六项全部端到端验证)
|
||||||
|
|
||||||
|
| # | 改动 | 验证 |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | `GetByConversationID` 改 `id DESC` 取最新 + 内存反转,limit 取 `conf.Limits.ChatMaxMessages`;游标配 DESC | 55 条会话实测渲染 55 条(改前 50);单测 3 项 + 变异(改回 ASC 变红) |
|
||||||
|
| 2 | 弃 `flamego.Classic()`,`flamego.New()` + 自写 `accessLogMiddleware`(只记 `URL.Path`;静态成功不记、4xx/5xx 记) | 日志 `msg=access path=/_/tblocal/4`,token 计数 0 |
|
||||||
|
| 3 | `signal.NotifyContext` + `srv.Shutdown(15s)` + 关连接池 | 8 个并发分享图请求中 SIGTERM → 8/8 成功 |
|
||||||
|
| 4 | 图片失败分支 `Questions.DeleteByID` 回滚 | 6MB 图 → 提问增量 0 且 `deleted_at` 非空;无图对照增量 1 |
|
||||||
|
| 5 | `cache.Cacher` 加 `if conf.Redis.Addr != ""`(否则 MemoryIniter);启动 `PingRedis` fail-fast | 停 Redis 后启动立即失败并指向 Redis |
|
||||||
|
| 6 | 投票 Page/Submit 加 L4、VotePublish 加 L2、探索页用 `ActiveLevels` 批量过滤 | L4 → `/vote` 302 到 /restricted;探索页该投票 0→2(撤销后);L2 下发布停 draft,撤销后 published |
|
||||||
|
|
||||||
|
新增 `vote.err.restrictedPublish` 四语言 key。回归:4/4、19/19、6/6、3/3,全量单测 + `go build ./...` 通过。
|
||||||
|
|
||||||
|
**踩到的两个坑**(值得记):
|
||||||
|
- 消息加载的兜底写成 `limit < 50 → 50`,会把箱主显式配置的小上限抬大,测试直接测出这个语义错误。
|
||||||
|
兜底只该处理**零值**(未初始化),不该替用户改配置。
|
||||||
|
- Python 的 **bytes 字面量不支持 `\uXXXX`**(只有 str 支持),写字面量 `b'...\uff08'` 会原样写入
|
||||||
|
`\uff08` 六个字符。CRLF 文件的字节级替换要用 `\xef\xbc\x88` 或 `.encode()`。
|
||||||
|
|
||||||
|
### 待办
|
||||||
|
|
||||||
|
六项改动仍未 commit;P2/P3 清单在 `outputs/review-2026-10-29.md` 第四、五节。
|
||||||
|
建议下一批:投票结果聚合 O(N²)、三处幂等缺陷、ReadTimeout 30s 与 8MB 上传冲突、Dockerfile 构建不出来。
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
明细已拆分到 `topics/*.md`(按需 Read 全文):
|
明细已拆分到 `topics/*.md`(按需 Read 全文):
|
||||||
`coop` 协作 / `build` 构建测试 / `embed` 行尾·模板·embed / `review` 审核处置举报 /
|
`coop` 协作 / `build` 构建测试 / `embed` 行尾·模板·embed / `review` 审核处置举报 /
|
||||||
`gorm` GORM / `style` 前端样式 / `theme` 主题 / `builder` 主题生成器 / `share` 分享卡 /
|
`gorm` GORM / `style` 前端样式 / `theme` 主题 / `builder` 主题生成器 / `share` 分享卡 /
|
||||||
`honeypot` 蜜罐多家族伪装 / `stats` 全站数据页 / `sec` 安全评估·客户端 IP 信任 / `misc` 其它约定。
|
`honeypot` 蜜罐多家族伪装 / `stats` 全站数据页 / `sec` 安全评估·客户端 IP 信任 /
|
||||||
|
`quality` 业务正确性·运维·前端(2026-10-29) / `misc` 其它约定。
|
||||||
|
|
||||||
## 协作
|
## 协作
|
||||||
|
|
||||||
@@ -40,6 +41,9 @@
|
|||||||
- `ListAPI` 不经 Pager 中间件,L4 与页面密码须自校验;分享卡拦截须在缓存查询之前。
|
- `ListAPI` 不经 Pager 中间件,L4 与页面密码须自校验;分享卡拦截须在缓存查询之前。
|
||||||
- 举报:免登录+验证码+限流;同 IP 同题唯一索引 → 幂等;箱主只看次数不给 IP。
|
- 举报:免登录+验证码+限流;同 IP 同题唯一索引 → 幂等;箱主只看次数不给 IP。
|
||||||
- 告警 24h≥8 或 7d≥20 → 邮件;`[security] security_alert_email` 为空则静默跳过。
|
- 告警 24h≥8 或 7d≥20 → 邮件;`[security] security_alert_email` 为空则静默跳过。
|
||||||
|
- **处置覆盖面:新增任何「用户发起」的写入口都要问一句「这里要不要查 `ActiveLevel`」**。
|
||||||
|
提问链路 7 处齐全;投票链路曾 0 处(L4 后 `/vote/{token}` 照常可投、可发新投票、探索页照常曝光),
|
||||||
|
2026-10-29 已补齐:`vote.Page`/`vote.Submit` 拦 L4、`VotePublish` 拦 L2、探索页按 `ActiveLevels` 批量过滤。
|
||||||
|
|
||||||
## GORM(topics/gorm.md)
|
## GORM(topics/gorm.md)
|
||||||
|
|
||||||
@@ -71,9 +75,14 @@
|
|||||||
否则 EO 线退化成按边缘 IP 聚合(限流互挤、自动封禁误伤整片)。`app.sample.ini` 已注明。
|
否则 EO 线退化成按边缘 IP 聚合(限流互挤、自动封禁误伤整片)。`app.sample.ini` 已注明。
|
||||||
- 跳转参数只走 `shared.SafeRedirectTarget`;`url.Parse` 会解 `%0d%0a`,必须逐 rune 查 `u.Path`。
|
- 跳转参数只走 `shared.SafeRedirectTarget`;`url.Parse` 会解 `%0d%0a`,必须逐 rune 查 `u.Path`。
|
||||||
- **同一 handler 里 GET 有所有权校验 ≠ POST 有**(私聊 `SendMessage` 曾漏,可跨箱骚扰)。逐分支核对。
|
- **同一 handler 里 GET 有所有权校验 ≠ POST 有**(私聊 `SendMessage` 曾漏,可跨箱骚扰)。逐分支核对。
|
||||||
- CSP 三档默认 `report`;`script-src` 必须含 `mami.shiroko.one`(umami 是动态注入,grep 抓不到)。
|
- CSP 默认 **enforce**(逃生口 `csp = report|off` 后重启);`script-src` 必须含 `mami.shiroko.one`
|
||||||
|
(umami 动态注入,grep 抓不到)与按 `sentry_dsn` 条件放行的 `browser.sentry-cdn.com`。
|
||||||
|
改 CSP 白名单后必须跑 `csp_sweep.py`(真浏览器巡检),静态检查不算数。
|
||||||
- 注册防枚举:邮箱重复/停用与正常注册共用同一个成功出口;代价是忘密只能走找回。
|
- 注册防枚举:邮箱重复/停用与正常注册共用同一个成功出口;代价是忘密只能走找回。
|
||||||
- 报告 `outputs/security-audit-2026-10-25.md` + 复现脚本 `outputs/sec-audit-2026-10-25/`。
|
- **`flamego.Classic()` 自带 `Logger()` 会记 `RequestURI`(含 query)** —— `?t=` 私密题钥匙、
|
||||||
|
`?code=` 改密凭证都会明文进日志(已实测)。要自写访问日志,只记 `URL.Path`。
|
||||||
|
- 报告 `outputs/security-audit-2026-10-25.md` + 复现脚本 `outputs/sec-audit-2026-10-25/`;
|
||||||
|
非安全向的复盘见 `outputs/review-2026-10-29.md`。
|
||||||
|
|
||||||
## 其它约定(topics/misc.md)
|
## 其它约定(topics/misc.md)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# 业务正确性 / 运维 / 前端体验(2026-10-29 第二轮复查)
|
||||||
|
|
||||||
|
安全向的结论在 `sec.md`。这里是非安全向的四类,完整清单见 `outputs/review-2026-10-29.md`。
|
||||||
|
|
||||||
|
> **状态**:下列「已实测确认的缺陷」「处置覆盖面」「Redis」三块已于同日修复并端到端验证,
|
||||||
|
> 条目保留是为了记住**为什么**会踩、以及验证该怎么做。P2/P3 仍未动。
|
||||||
|
|
||||||
|
## 已修:两个 P0(都是实测出来的)
|
||||||
|
|
||||||
|
- **私聊 50 条截断(P0,已修)**:`internal/db/message.go` 的 `GetByConversationID` 是
|
||||||
|
`Order("created_at ASC") + Limit(50)`,而唯一调用方 `chat_handler.go:97` 永远传 `nil` cursor。
|
||||||
|
会话上限是 200 → 第 51 条起写库成功、`message_count` 在涨、页面显示 51/200,但列表永远只有最早 50 条。
|
||||||
|
实测造 55 条,页面只渲染 #1–#50。**取最新 N 条要 `Order("id DESC")` 再内存反转**;
|
||||||
|
游标 `id <` 也必须配 DESC(配升序会反复返回同一页)。
|
||||||
|
- **访问日志泄露 token(P0,已修)**:`flamego.Classic()` = `Use(Logger(), Recovery(), Static())`,
|
||||||
|
`Logger()` 记的是 `ctx.Request().RequestURI`(含 query)。实测出现
|
||||||
|
`path=/_/tblocal/4?t=SECRETTOKEN123`。`?t=` 是私密题钥匙,`?code=` 是改密凭证。
|
||||||
|
**tracing 中间件早就为同一风险避开 RequestURI 并写了注释,框架默认 Logger 漏了。**
|
||||||
|
改用 `flamego.New()` + 自写访问日志,只记 `URL.Path` 与白名单 query key。
|
||||||
|
|
||||||
|
## 处置(restrict)的覆盖面(投票已补齐)
|
||||||
|
|
||||||
|
- 提问链路 7 处 `ActiveLevel`(page L4 / question L3 / setPublic L2 / followUpAnswer L2 / shareCard 全级 / ListAPI L4 / moderation 锁定)。
|
||||||
|
- **投票链路 0 处**:`vote.Page`、`vote.Submit`、`VotePublish` 都不查。L4 处置后 `/_/domain` 已 302 到
|
||||||
|
`/restricted`,但 `/vote/{token}` 照常可投、可发新投票、探索页照常曝光。
|
||||||
|
- **教训:新增任何「用户发起」的写入口,都要问一句「这里要不要查 ActiveLevel」。**
|
||||||
|
|
||||||
|
## 运维面
|
||||||
|
|
||||||
|
- **无优雅关闭**(已修):全仓 `signal.Notify` / `server.Shutdown` 零命中,SIGTERM 直接杀进程。
|
||||||
|
`web.go` 里 `ListenAndServe` 一把梭。每次部署都掐断在途 POST。
|
||||||
|
- **Redis 两种故障**(已修):session 走 Redis 时 load 失败会 `panic`(flamego/session),被 Recovery 兜成 **全站 500**;
|
||||||
|
`cache.Cacher` 没写 `if conf.Redis.Addr != ""`,空 Addr 被 go-redis 回退 `localhost:6379` →
|
||||||
|
「不配 Redis」的部署里找回密码静默坏掉、私信邮件节流失效。启动时该 `PING` 一次并 fail fast。
|
||||||
|
- `ReadTimeout: 30s` 与 8MB 单图上限冲突(手机 4G 上行 1–2 Mbps → 8MB 必超时),正文也一起丢。
|
||||||
|
- 无 `/healthz`:`route.Home` 不碰 DB,DB 挂了 `/` 仍 200。
|
||||||
|
- `Dockerfile` 用 `golang:1.20` 而 `go.mod` 是 1.27.3 → **构建必失败**;`-X` 路径还是旧仓库名
|
||||||
|
`github.com/NekoWheel/NekoBox`,`BuildCommit` 永远是 `dev`。
|
||||||
|
- AutoMigrate 无版本化:GORM 只加列不删列,**改列名会静默留下旧列 + 新列全 NULL**,比迁移失败更难发现。
|
||||||
|
- 备份只有 `docs/cli.md` 一句 `pg_dump`;用户图片全在对象存储,**只恢复 DB = 全站裂图**。
|
||||||
|
|
||||||
|
## 前端
|
||||||
|
|
||||||
|
- `templates/question/history-template.html` 有硬编码中文(查看回答 / 加载更多 / 无更多留言),en/ja 用户直接看到。
|
||||||
|
`vote/page.html:140` 中英混排。**字典本身 917 条 × 4 语言完全对齐(有测试守着),问题只在模板层。**
|
||||||
|
- 提问页验证码失败走 `SetErrorFlash + Redirect` → **整段提问内容丢弃**;其他分支都是
|
||||||
|
`SetError + Success` 保留内容。最常见的失败反而最不该丢内容。
|
||||||
|
- `form.submit()` 不触发 submit 事件 → 验证码流(默认 captcha_type)完全没有防重复点击。
|
||||||
|
- `waline.js` 全站加载但只有 `home.html` / `item.html` 用;UIkit CSS 走第三方 CDN 且阻塞首屏。
|
||||||
|
- 32 个 `<label>` 无 `for=`;语义标签白字对比度约 1.9:1;全仓零 `loading="lazy"`。
|
||||||
|
- 移动端 `body{padding-bottom:56px}` 与 `env(safe-area-inset-bottom)` 冲突,iPhone 上遮住约 32px。
|
||||||
|
|
||||||
|
## 产品缺口(按 ROI)
|
||||||
|
|
||||||
|
1. **提问者找不回自己的提问**:不填邮箱时唯一手段是成功页那条链接,关页即失联。
|
||||||
|
全仓 `localStorage` 零命中。最小解:前端记 `{domain, id, token}`,箱主页显示「你有 N 条提问已被回答」。
|
||||||
|
2. **箱主没有自己的数据**:`SiteAnalytics` 是全站聚合(只有 `COUNT(DISTINCT user_id)`),
|
||||||
|
**全站没有 page view 表**。箱主不知道自己被看了多少次、回答率多少。
|
||||||
|
3. `NotifyType` 只有 `email` / `none`,无 Telegram / Webhook。
|
||||||
|
4. 模板零 `application/ld+json`(SEO 富摘要),无 RSS。
|
||||||
|
5. 图片原图直传,无前端压缩。
|
||||||
|
|
||||||
|
## 这一轮确认「不用动」的
|
||||||
|
|
||||||
|
四语言字典对齐、深色模式正文对比度 4.8–6.8:1、gzip 实现(204/304/Content-Length/Vary/Flusher 都处理对了)、
|
||||||
|
后台移动端短标签方案。这几块质量高于平均,别为了统一风格去改。
|
||||||
@@ -83,7 +83,11 @@ CSRF 绑 uid(无 token POST → 400);18 条 XSS 载荷全灭(含实体
|
|||||||
`NotoSansCJK-Regular.ttc` 是 OTTO → 就算 `ttcFirstFont()` 正确提取出子字体也仍不可用。
|
`NotoSansCJK-Regular.ttc` 是 OTTO → 就算 `ttcFirstFont()` 正确提取出子字体也仍不可用。
|
||||||
`.ttc` 是字体集合,需先按 `ttcf` 魔数 → `numFonts` → 子字体偏移 → 重排 sfnt header + 表目录
|
`.ttc` 是字体集合,需先按 `ttcf` 魔数 → `numFonts` → 子字体偏移 → 重排 sfnt header + 表目录
|
||||||
(4 字节对齐)才能交给 freetype(`route/question/ttc.go`,测试 `ttc_test.go` 自造最小 TTC)。
|
(4 字节对齐)才能交给 freetype(`route/question/ttc.go`,测试 `ttc_test.go` 自造最小 TTC)。
|
||||||
生产机上要备一个 **glyf 轮廓**的 TTF(如 LXGWWenKai-Medium.ttf)放 `static/fonts/` 或配 `font_path`。
|
|
||||||
|
**2026-10-27 已收敛**:`fontCandidates()` 补 glyf 系统候选(wqy-zenhei / wqy-microhei /
|
||||||
|
DroidSansFallbackFull / uming)且排在 Noto(CFF) 之前;`static/fonts` 同时按 CWD 与可执行文件目录找;
|
||||||
|
`CheckShareCardFont()` 在 `runWeb` 启动时探一次(Info 打路径 / WARN 给 `apt install fonts-wqy-zenhei`)。
|
||||||
|
本地实测该 apt 包即可零配置出图(`/share-card` 500 → 200 image/jpeg)。生产同理,别用 Noto CJK。
|
||||||
|
|
||||||
## 仍待决策/未做
|
## 仍待决策/未做
|
||||||
|
|
||||||
@@ -91,7 +95,11 @@ CSRF 绑 uid(无 token POST → 400);18 条 XSS 载荷全灭(含实体
|
|||||||
- 匿名投票清 cookie 即换身份:只加了结果页匿名说明,未做 IP 唯一索引(会被 NAT/移动网误伤)。
|
- 匿名投票清 cookie 即换身份:只加了结果页匿名说明,未做 IP 唯一索引(会被 NAT/移动网误伤)。
|
||||||
- 颜色字段只校验长度(有 Go 上下文转义兜底,非安全问题)。
|
- 颜色字段只校验长度(有 Go 上下文转义兜底,非安全问题)。
|
||||||
- P1-1 残余:存量内容里的外链 img 仍在库里,仅渲染层不再输出;将来若加「原样导出」入口会复发。
|
- P1-1 残余:存量内容里的外链 img 仍在库里,仅渲染层不再输出;将来若加「原样导出」入口会复发。
|
||||||
- CSP 目前停在 `report`:7 个公开页面实测零违规,可以切 `enforce`,等用户拍板。
|
- CSP 默认 **enforce**(2026-10-27 定):不给 `csp_report_uri` 的 report-only 等于没装,
|
||||||
|
留个不生效的开关更容易让人误以为已防护。白名单由 `csp_sweep.py` 真浏览器巡检背书
|
||||||
|
(18 页面 + 2 图片端点,零违规);误伤时的逃生口是 `csp = report|off` + 重启,不用改代码。
|
||||||
|
- **Sentry CDN 是条件白名单**:`header.html` 的 Sentry 在 `{{ if ne SentryDSN "" }}` 里,
|
||||||
|
静态 grep 和本地巡检都碰不到 → 按 `conf.App.SentryDSN` 条件放行,单测双向断言。
|
||||||
|
|
||||||
## 复现脚本怎么跑(踩过的两次假红,别再误判成回归)
|
## 复现脚本怎么跑(踩过的两次假红,别再误判成回归)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user