224 lines
21 KiB
Markdown
224 lines
21 KiB
Markdown
# 《Pure Note 设计方案 v1.0》审评报告(第 1 轮)
|
||
|
||
> 审评对象:docs/design.md(v1.0,2026-09-07)
|
||
> 审评日期:2026-09-07
|
||
> 审评方法:作者自查 + 两路独立交叉评审(安全攻防视角 / 工程可行性视角),关键争议点在线核实(Vite 官方文档、rehype-highlight README、@codemirror/view 与 style-mod 源码)。所有发现均已逐条对照原文验证。
|
||
|
||
---
|
||
|
||
## 一、总体结论
|
||
|
||
**方案骨架成立,选型与架构方向无需推翻;但按现稿直接开工会在 4 个 P0 问题上翻车**(2 个功能性硬伤、2 个私密内容泄露面),契约层还埋有 11 个 P1 雷。修订后可进入 M0。
|
||
|
||
| 级别 | 定义 | 数量 |
|
||
| --- | --- | --- |
|
||
| P0 | 按文档字面实现即失败或泄露私密内容,实现前必须修订 | **4** |
|
||
| P1 | 重要缺陷/缺口,不修会在对应模块返工或留下安全弱点 | **11** |
|
||
| P2 | 加固与工程化改进 | **9** |
|
||
|
||
---
|
||
|
||
## 二、发现汇总
|
||
|
||
| # | 级别 | 位置 | 问题一句话 |
|
||
| --- | --- | --- | --- |
|
||
| 1 | P0 | §8.3 | Vite `base: './'` 与 SPA 深链路由冲突,二级路径全部白屏 |
|
||
| 2 | P0 | §5 / §10.1 | go:embed 跨包引用不可实现,构建链缺 dist 同步步骤,静默嵌入陈旧前端 |
|
||
| 3 | P0 | §7.4 | 私有图片响应 `Cache-Control: public, immutable` 授权共享缓存存储私密内容 |
|
||
| 4 | P0 | §8.3 | index.html 元信息注入未做可见性过滤,私有笔记标题/摘要泄露给匿名爬虫 |
|
||
| 5 | P1 | §7.3 / §8.2 | CSRF token 生命周期断裂:刷新后无法再获取、轮换语义未定义 |
|
||
| 6 | P1 | §7.1 | 管理员预览私有笔记的数据通路不存在(公开详情 404,管理接口只有编辑态) |
|
||
| 7 | P1 | §7.1 / §6.1 | settings 接口无字段白名单:口令哈希可能被回显/覆写;且无改密流程 |
|
||
| 8 | P1 | §7.1 | `/api/tags` 未写公开过滤,私有笔记的标签成为信息泄露面 |
|
||
| 9 | P1 | §7.1 / §8.2 | slug 自动生成不满足「唯一」承诺(同日必冲突),定稿时机未定义 |
|
||
| 10 | P1 | §6.2 / §7.6 | gc 无宽限期:编辑中已上传未保存的图片会被物理删除 |
|
||
| 11 | P1 | §7.1 | 笔记硬删除无回收站,误删当天内容不可恢复 |
|
||
| 12 | P1 | §7.2 | JSON 接口无请求体大小限制、服务器无超时(匿名单请求 OOM / slowloris) |
|
||
| 13 | P1 | §10.4 | 升级回滚说法不严谨:旧二进制遇更高 user_version 会静默在陌生 schema 上读写 |
|
||
| 14 | P1 | §12 / §9.3 | 测试策略整体缺失,可见性/越权矩阵无自动化回归 |
|
||
| 15 | P1 | §10.3 | 备份文件是全量明文私密数据:权限、加密、恢复细节未设防 |
|
||
| 16 | P2 | §7.1 / §9.3 | 图片未授权应统一 404(防 ID 枚举),文档未明确 404 vs 403 |
|
||
| 17 | P2 | §9.2 / §10.2 | HSTS 在威胁矩阵里声称了,但安全头清单和 Caddyfile 都没有 |
|
||
| 18 | P2 | §9.2 | `style-src 'unsafe-inline'` 的理由一半错误;nonce 方案可达严格 CSP |
|
||
| 19 | P2 | §7.2 | `/api/auth/*` 不在 Origin 校验分支(login CSRF + 令牌桶 DoS 放大) |
|
||
| 20 | P2 | §13 | 「XFF 可信首跳」表述含混(首/尾跳取错则限流整体失效) |
|
||
| 21 | P2 | §7.3 | Argon2id 未规定 PHC 编码存储(参数升级将无法校验旧哈希) |
|
||
| 22 | P2 | §7.1 | 分页参数无边界约定 |
|
||
| 23 | P2 | §10.3 | cron 备份与 systemd 沙箱的关系未声明(结论:不冲突,但需写明前提) |
|
||
| 24 | P2 | §10 / §12 | 优雅停机、日志选型(slog)、开发热重载工作流三处缺口 |
|
||
|
||
---
|
||
|
||
## 三、P0 详细说明
|
||
|
||
### P0-1 Vite `base: './'` 与 SPA 深链冲突(§8.3)
|
||
|
||
**问题**:文档规定 `base: './'`(来自第一轮调研结论「绝对路径会 404」——**该论断不成立,本轮已用 Vite 官方文档推翻**)。相对 base 适用于「部署路径事先未知」或 file:// 嵌入场景;本项目部署在域名根,恰恰是反面用例。
|
||
|
||
**推导**:`base: './'` 时 index.html 资源引用为 `./assets/index-HASH.js`。浏览器深链访问 `/notes/my-post`(SPA fallback 返回 index.html,地址栏不变),按 RFC 3986 相对解析:`/notes/my-post` 的目录为 `/notes/`,故解析为 `/notes/assets/index-HASH.js` → 未命中 embed FS → fallback 返回 200 + `text/html` → module script 因 MIME 校验拒绝执行 → **React 永不启动,白屏**。
|
||
|
||
**波及范围**:`/notes/:slug`、`/tags/:tag`、`/admin/login`、`/admin/notes/:id/edit` 等所有二级路径的刷新、深链、外链分享。且 `vite dev` 与客户端导航均正常,**只在嵌套路由初始加载时爆发**,测试不充分必然漏网。
|
||
|
||
**修复**:
|
||
- `vite.config.ts` 使用默认 `base: '/'`;embed handler 从站点根提供 FS,绝对路径恰好正确命中;
|
||
- 删除文档中「必须 `base: './'`」及「绝对 /assets/ 会 404」的表述;
|
||
- CI 增加冒烟测试:真实构建 → 启动二进制 → 请求任一公开 slug 页面 → 断言 script/link 以 200 + 正确 MIME 加载。
|
||
|
||
### P0-2 go:embed 跨包引用不可实现(§5 / §8.3 / §10.1)
|
||
|
||
**问题**:构建产物输出在 `web/dist`,嵌入声明却在 `internal/webui` 包。`//go:embed` 只能嵌入本包目录树内的文件(不允许 `..`、不能跨包)。Makefile 的 `web` 目标止步于 `npm run build`,没有把 `web/dist` 同步进 `internal/webui/dist` 的步骤;M0 又要求「embed 空 dist」——占位文件先入库后,**编译永远不报错,二进制里嵌的是陈旧前端**,是典型「我明明改了前端」返工源。
|
||
|
||
**修复**(二选一,写入 §8.3):
|
||
- (a) Makefile 增加 `cp -r web/dist/ internal/webui/dist/`;`internal/webui/dist` 进 .gitignore,仓库保留占位文件供 M0 编译;
|
||
- (b) embed 上提到仓库根包:`//go:embed all:web/dist` + `fs.Sub`(无需拷贝,但根包与前端目录耦合)。
|
||
|
||
### P0-3 私有图片缓存头授权共享缓存存储(§7.4)
|
||
|
||
**问题**:文档对所有图片一刀切 `Cache-Control: public, max-age=31536000, immutable`。`public` 指令(RFC 9111)明确授权**共享缓存**存储该响应——即使是对带凭据请求的答复。
|
||
|
||
**攻击链**:管理员在 TLS 拦截代理环境浏览私有笔记 → 代理缓存 `GET /api/images/47` → 同网络任何人请求同 URL 直接命中缓存,**不回源、绕过鉴权与日志**;`images.id` 自增,`/api/images/1..N` 可批量收割。另有两条独立伤害:笔记 public→private 切换后,已缓存副本因 `immutable` 一年内不再回源,违反 F2「切换即时生效」;登出后磁盘缓存仍可离线回放。
|
||
|
||
**修复**:
|
||
- 按可见性分流:公开图 `public, max-age=31536000, immutable`;非公开图 `private, no-store`(或 `private, max-age=0, must-revalidate`,且再验证必须重新执行可见性判定而非仅比 ETag);
|
||
- `/api/admin/*` JSON 响应统一 `Cache-Control: no-store`(防登出后 bfcache/返回键回看私有列表);
|
||
- 文档显式承认残余边界:「图片一旦随公开笔记暴露过,已分发副本(浏览器缓存/爬虫/RSS 阅读器)无法撤回」。
|
||
|
||
### P0-4 元信息注入泄露私有笔记(§8.3)
|
||
|
||
**问题**:§8.3 要求「请求 /notes/{slug} 时注入该笔记的 title/description/og meta」,但**没有写可见性过滤条件**;T10 的「单一可信点」也没把这条路纳入。叠加「非 API 路径一律 200 fallback」与默认 slug `post-YYYYMMDD` 的可预测性:攻击者逐个请求候选 slug,即使 SPA 渲染 404,**HTML 源码里已带私有笔记的标题与 og:description**。
|
||
|
||
**修复**:
|
||
- 注入查询与公开详情接口走同一条 `status='public'`(或管理员会话)规则,明确写入文档;
|
||
- 非公开一律回退站点默认 meta;
|
||
- 注入用 `html/template` 自动转义(或占位符替换),防标题内容打断标签结构注入任意 meta。
|
||
|
||
---
|
||
|
||
## 四、P1 详细说明
|
||
|
||
### P1-5 CSRF token 生命周期断裂(§7.3 / §8.2)
|
||
|
||
只在登录响应下发一次、仅存内存,`/api/me` 只回 `{authenticated}`。浏览器 F5 后会话 cookie 仍有效但 CSRF token 丢失 → 全部变更请求 403,实现者最可能的「自救」是把 token 落 localStorage(降级)或放宽校验(更糟),两者都是文档逼出来的。「距过期 <3 天旋转 token」也未规定 csrf_token 是否随新会话行重建。
|
||
**修复**:`GET /api/me` 在已认证时返回 csrf_token(同源 JSON,跨域读不到);会话轮换时保持 csrf_token 不变或在轮换响应中同步下发;明确禁止持久化;补充 dev 方案(`__Host-` 要求 Secure,`http://局域网IP` 登不上——dev 放宽开关必须仅 loopback 监听时生效)。
|
||
|
||
### P1-6 管理员预览私有笔记的通路缺失(§7.1)
|
||
|
||
公开 `GET /api/notes/{slug}` 对私有一律 404,管理接口只有按数字 id 的编辑态数据——前端 Note 页没有任何能取到私有笔记正文的数据源,「博客皮肤预览私有笔记」功能不存在。
|
||
**修复**:详情接口可见性规则定义为 `status='public' OR 有效管理员会话`(响应带 `status` 字段,前端显示「私有预览」横幅);meta 注入同规则。
|
||
|
||
### P1-7 settings 无字段白名单,口令哈希面临回显与覆写(§7.1 / §6.1)
|
||
|
||
`admin_password_hash` 与站点配置混存 settings 表;`GET/PUT /api/admin/settings` 未约定 DTO。朴素实现 `SELECT *` 即把 Argon2id 哈希下发浏览器(任何 XSS 即外泄凭证验证器);PUT 若接受任意 key,可携管理员会话直接覆写哈希完成「免旧密码改密」。全文也没有 init 之外的改密流程。
|
||
**修复**:显式 SettingsDTO(仅 site_title/site_desc/page_size),读永不序列化哈希、写只收白名单键;新增 `POST /api/admin/password {old, new}`,复用登录限流。
|
||
|
||
### P1-8 `/api/tags` 未写公开过滤(§7.1 / §9.1)
|
||
|
||
T10 点名了 API/RSS/sitemap/图片,唯独漏了 tags;API 表里只写「标签聚合」。私有笔记的敏感标签(病情、辞职、项目名)可被 `/api/tags` 与候选词探测确认。
|
||
**修复**:tags 聚合 SQL 统一加 `status='public'`;标签页同源过滤;§9.3 检查单补 tags 断言。
|
||
|
||
### P1-9 slug 生成不满足「唯一」承诺(§7.1 / §8.2)
|
||
|
||
§7.1 承诺「自动生成唯一 slug」,§8.2 方案是日期式 `post-20260907`——同日第二篇必然 UNIQUE 冲突,而文档把解冲突推给用户(「给出明确错误」),自相矛盾。slug 何时定稿(新建即分配还是首次保存)、自动保存下改标题是否重生成,均未定义。
|
||
**修复**:自解冲突(追加 `-2`/`-3` 或 4 位随机尾缀);「slug 仅首次保存定稿,此后除非手改不变」;冲突返回 409 + 字段级错误。
|
||
|
||
### P1-10 gc 误删编辑中的图片(§6.2 / §7.6)
|
||
|
||
图片在粘贴瞬间即上传入库,笔记保存前是 0 引用孤儿;gc 策略只有「清理 0 引用」没有时间条件,调度也未定义——长文编辑期间跑一次 gc,刚粘贴的图片被物理删除,保存后满屏死图。另外没有任何列出孤儿图片的管理接口,「孤儿仅管理员可见」实际谁也看不见。
|
||
**修复**:gc 只删 `created_at < now − 7天` 且 0 引用的图片;默认 `--dry-run`、`--commit` 才执行;调度写明(systemd timer 每日);补 `GET /api/admin/images?orphan=1` 供检视。
|
||
|
||
### P1-11 硬删除无回收站(§7.1)
|
||
|
||
恢复路径只有「停服 + 整库回滚到 03:00 备份」:当天新建又误删的笔记不在任何备份里,永久丢失;即便有备份,恢复单篇要整库回滚,会连带丢其他笔记当天改动。同类产品惯例(Notion/Google Docs 回收站 30 天、Obsidian 移入系统回收站)——「个人笔记」恰是最不该裸删的数据。
|
||
**修复**:最低成本方案——DELETE 时把笔记 JSON(含 refs)写入 tombstone 表,gc 30 天后清除,管理端加「最近删除」恢复入口;或 `notes` 加 `deleted_at` 软删列 + 查询统一过滤。
|
||
|
||
### P1-12 请求体限制与服务器超时缺失(§7.2)
|
||
|
||
5MB 限制仅在上传接口。`POST /api/auth/login` 是匿名可打的 JSON 接口,无 `http.MaxBytesReader`,单次请求即可提交任意大 body(per-IP 限流挡不住「一个」大请求);`ReadHeaderTimeout/ReadTimeout/IdleTimeout` 未提及(slowloris 面)。
|
||
**修复**:路由级 `http.MaxBytesReader`(login/settings 4–64KB、notes JSON 1MB、multipart 6MB 含开销);`http.Server{ReadHeaderTimeout: 5s, ReadTimeout: 60s, IdleTimeout: 120s}`,写入 §7.2。
|
||
|
||
### P1-13 升级回滚不严谨(§10.4)
|
||
|
||
「回滚 = 换回旧二进制(迁移可逆或旧版本可忽略新表)」不成立:`user_version` 是单向整数,旧二进制看到更高版本**不会报错**,会静默在陌生 schema 上读写,把可恢复事故变成不可恢复。「向前兼容」一词也用反了。
|
||
**修复**:启动时校验 `user_version ≤ 代码支持的最高版本`,超出拒绝启动(留 `--allow-newer` 旗标);迁移策略声明为「仅追加式」(禁止删列/重命名/改类型);升级 SOP 第一步固定为 `pn backup`。
|
||
|
||
### P1-14 测试策略缺失(§12 / §9.3)
|
||
|
||
五个里程碑无一提及测试;§9.3 是一次性人工检查单。本项目安全性的核心不变量恰是可见性/越权矩阵——{public, private} × {匿名, 管理员} × {列表, 详情, 图片, RSS, sitemap, meta 注入, tags}——最适合表驱动自动化;后续任何重构(尤其 §13 预留的 v2 SSR 化)都可能无声破坏 T10。
|
||
**修复**:Go 集成测试(httptest + 临时目录真实 SQLite)把矩阵做成表驱动用例,另加迁移幂等、限流、CSRF、魔数校验各一组;纳入 CI,从 M2 起随功能交付。
|
||
|
||
### P1-15 备份文件敏感性与恢复 SOP(§10.3)
|
||
|
||
备份 = 全量私有笔记 + 图片 BLOB + 口令哈希 + 会话哈希的**明文副本**。crontab 若以 root 运行、umask 022,备份文件 0644,同机任意本地用户可读全部私密内容;rclone 异地同步也是明文。恢复步骤未要求删除残留 `-wal`/`-shm`(旧 WAL 会污染还原后的库);「备份保留 30 天」与「删除笔记」的数据语义关系未声明。
|
||
**修复**:备份文件 chmod 0600 / 专用低权用户;异地前强制加密(age / rclone crypt);恢复 SOP 补「删除 `pn.db-wal` 与 `-shm`」;声明备份保留与删除的关系。
|
||
|
||
---
|
||
|
||
## 五、P2 简述
|
||
|
||
| # | 修复建议 |
|
||
| --- | --- |
|
||
| 16 | 图片未授权与不存在统一返回 404(与笔记 slug 一致,防自增 ID 枚举);§9.3 改为明确断言 + 遍历测试用例 |
|
||
| 17 | `Strict-Transport-Security: max-age=31536000` 写入 SecurityHeaders 中间件(经反代透传有效)或 Caddyfile,消除 T11 与 §9.2 的不一致 |
|
||
| 18 | 已核实:rehype-highlight **不注入任何样式**(只加 class),§9.2 把它列为 `unsafe-inline` 理由是错的;CodeMirror 经 style-mod 注入 `<style>` 属实,但 `@codemirror/view` 提供 `EditorView.cspNonce`——每响应由 Go 生成 nonce 注入 CSP + 前端传入,即可删掉 `'unsafe-inline'`。v1 可保守保留,但须修正注释并规定 rehype-sanitize schema 永不放开 `style` 属性;公共路由(无编辑器)可先单独下发严格 CSP |
|
||
| 19 | 所有 POST(含 `/api/auth/*`)统一 Origin/Referer → Host 校验:防 login CSRF,也堵「跨站表单无门槛消耗账号令牌桶(5 次/10 分钟)把真管理员锁在门外」的 DoS 放大 |
|
||
| 20 | 明确「唯一可信代理是 Caddy,取 XFF **最右**一个条目」(或让 Caddy 覆盖而非追加 XFF);per-IP 桶设数量上限 + TTL 清理,防伪造 IP 撑爆内存 |
|
||
| 21 | Argon2id 存 PHC 串 `$argon2id$v=19$m=19456,t=2,p=1$<b64salt>$<b64hash>`,参数随哈希走,未来调参可校验旧哈希 |
|
||
| 22 | page_size 夹取 [1,100](默认 10),page 夹取 [1,10⁴],越界 400 |
|
||
| 23 | 写明「备份以独立进程运行,不受 unit 沙箱约束;/backup 属主与权限要求」;建议 cron 改 systemd timer(`OnCalendar=03:00, Persistent=true`),日志进 journal |
|
||
| 24 | 补三件事:SIGTERM → `http.Server.Shutdown` + SQLite 干净关闭;日志选型 `log/slog`(text/JSON 双模式,登录失败记 IP 与计数);`make dev` 一键起双端(`vite dev` 配 `server.proxy` 转发 `/api` 到 127.0.0.1:8080——同源 cookie/CSRF 才成立,Go 侧 air 热重载) |
|
||
|
||
---
|
||
|
||
## 六、争议点裁定(本轮在线核实)
|
||
|
||
| 争议 | 裁定 | 依据 |
|
||
| --- | --- | --- |
|
||
| Vite `base: './'` vs `'/'` | **`'/'`(默认)**。第一轮调研结论「绝对路径会 404」不成立:embed handler 从站点根提供 FS,绝对路径恰好命中;相对 base 在深链下解析到错误目录 | Vite 官方文档(Public Base Path:relative base 适用于「路径事先未知」与嵌入场景)+ RFC 3986 相对解析推导 |
|
||
| `style-src 'unsafe-inline'` 是否必需 | 对 CodeMirror 成立(style-mod 注入 `<style>`),对 rehype-highlight 不成立(纯 class);且 nonce 方案可去掉 `unsafe-inline` | rehype-highlight README;style-mod v4.1.3 README(`StyleModule.mount` 支持 nonce);@codemirror/view `EditorView.cspNonce` facet 源码 |
|
||
| cron 备份与 systemd 沙箱是否冲突 | **不冲突**(沙箱只约束服务进程,管不到 crontab 拉起的独立进程;`VACUUM INTO` 在 WAL 下与 serve 并发安全),但前提需写明 | systemd 语义 + SQLite WAL 并发模型 |
|
||
| sha256 去重 + image_refs 并集语义 | 规则自洽但需文档化:同一图片字节被公开笔记引用即整体公开(可见性 = 所有引用的**并集**);「曾公开即视为已分发」须作为已知边界写入 | 设计内审 |
|
||
|
||
---
|
||
|
||
## 七、设计得当之处(维持不动)
|
||
|
||
1. 可见性收敛为服务端查询层单一可信点(T10),前端标识仅作展示——全文最值钱的决策;
|
||
2. 图片 BLOB 入库 + 魔数白名单不含 SVG——同时消灭 WebShell 执行面与 SVG 存储型 XSS 两类风险,且无路径穿越面;
|
||
3. 会话库存 SHA-256 摘要而非明文 token,备份/DB 泄露不直接等价会话劫持;
|
||
4. 只存 Markdown 原文、渲染期双层清洗 + CSP 兜底,安全策略升级可回溯覆盖全部存量笔记;
|
||
5. 高亮选 class-based 的 rehype-highlight 方向正确(已核实零内联样式);
|
||
6. WAL + busy_timeout + `VACUUM INTO` 在线快照 + `SetMaxOpenConns(1)`,与个人规模严格匹配;
|
||
7. ADR 记录、被否决备选、版本在线核实的文档纪律。
|
||
|
||
---
|
||
|
||
## 八、v1.1 修订清单(对 design.md 的具体动作)
|
||
|
||
| 动作 | 涉及发现 |
|
||
| --- | --- |
|
||
| §8.3:`base` 改为默认 `'/'`,删除「必须 './'」及「绝对路径 404」论断;新增「构建冒烟测试」条目 | P0-1 |
|
||
| §5 / §10.1:确定 embed 机制(建议:Makefile 拷贝 dist → internal/webui/dist + .gitignore + 占位文件),Makefile 补拷贝步骤 | P0-2 |
|
||
| §7.4:图片缓存头按可见性分流;§7.2 或 §9.2:`/api/admin/*` 响应 `no-store`;§6.2 或 §9.1:写入并集可见性与「曾公开即已分发」边界 | P0-3、P1(语义) |
|
||
| §8.3:meta 注入可见性规则(public 或管理员会话,否则站点默认 meta)+ `html/template` 转义 | P0-4 |
|
||
| §7.1:`GET /api/me` 认证态返回 csrf_token;§7.3:轮换语义、禁 localStorage、dev cookie 方案 | P1-5 |
|
||
| §7.1:详情接口规则 `public OR 会话`,响应带 status;§8.1:「私有预览」横幅 | P1-6 |
|
||
| §7.1:SettingsDTO 白名单 + `POST /api/admin/password`;§6.1:PHC 编码 | P1-7、P2-21 |
|
||
| §7.1:`/api/tags` 明确 public 过滤;图片未授权统一 404 | P1-8、P2-16 |
|
||
| §7.1 / §8.2:slug 自解冲突算法 + 定稿规则 + 409 | P1-9 |
|
||
| §6.2 / §7.6:gc 宽限 7 天 + dry-run/commit + systemd timer + `GET /api/admin/images?orphan=1` | P1-10、P2-23(半) |
|
||
| §6 / §7.1:回收站(建议 tombstone 表,30 天,恢复入口) | P1-11 |
|
||
| §7.2:分级 MaxBytesReader + Server 超时;`/api/auth/*` 纳入 Origin 校验 | P1-12、P2-19 |
|
||
| §10.4:user_version 上界校验拒绝启动 + 仅追加式迁移 + 升级先备份 | P1-13 |
|
||
| 新增 §「测试策略」:可见性矩阵表驱动集成测试 + CI,纳入 M2–M4 验收 | P1-14 |
|
||
| §10.3:备份 0600 + 加密异地 + 恢复删 `-wal/-shm` + 保留语义;systemd timer 替代 cron | P1-15、P2-23 |
|
||
| §9.2:HSTS 补入;修正 `unsafe-inline` 注释,写明 nonce 升级路径与 sanitize schema 禁 `style` 属性 | P2-17、P2-18 |
|
||
| §13:XFF 改「最右一跳」+ 限流桶上限 | P2-20 |
|
||
| §7.1:分页边界 | P2-22 |
|
||
| §10 / §12:优雅停机、slog、`make dev`(vite proxy + air) | P2-24 |
|
||
|
||
---
|
||
|
||
## 九、结论
|
||
|
||
v1.0 的架构与选型判断经受住了对抗性审评(无一条要求推翻 ADR 级决策),但在「前端构建交付链」和「私密内容的缓存/元信息边界」上存在 4 个 P0 硬伤——其中 Vite base 一条源于第一轮调研结论本身的错误,属于**调研结论未复核即采纳**的流程教训。按第八节修订清单出 v1.1 后,方可进入 M0 开发。
|