92 lines
7.7 KiB
Markdown
92 lines
7.7 KiB
Markdown
# 最终验收报告
|
||
|
||
> 依据:docs/design.md(v1.1);决策记录:docs/decisions.md
|
||
> 日期:2026-09-08
|
||
|
||
## 一、验收结论
|
||
|
||
**全部通过。** 设计文档定义的功能(§2.1 F1–F8)、安全设计(§9)、测试策略(§13)
|
||
与里程碑验收门(§12 M0–M4)均已实现并有自动化测试或实测证据支撑。
|
||
`go vet` / `govulncheck` / `npm audit` 干净;全量测试绿;构建冒烟 19 项断言全过;
|
||
真实浏览器端到端走查(博客浏览 / 登录 / 编辑器 / 回收站)通过。
|
||
|
||
## 二、§9.3 上线前安全检查单(逐项)
|
||
|
||
| 检查项 | 状态 | 证据 |
|
||
| --- | --- | --- |
|
||
| go vet + govulncheck 干净;npm audit 干净 | ✅ | `go vet ./...` 通过;`govulncheck ./...`:0 个影响本代码的漏洞;`npm audit --omit=dev`:0 vulnerabilities |
|
||
| 可见性矩阵自动化用例全绿(列表/详情/标签/RSS/sitemap/图片直链/HTML meta/管理接口) | ✅ | `TestVisibilityMatrix`(表驱动:{匿名,管理员} × {公开,私有,回收站,不存在} × 7 出口 + 图片缓存头分流 + 404 防枚举一致性) |
|
||
| 恶意 Markdown 渲染无脚本执行 | ✅ | 前端 vitest 冒烟(script/iframe/事件属性/javascript:/style/SVG 全拦截 + schema 快照);服务端 goldmark 转义 + bluemonday(`TestRenderEscapesDangerousHTML`);CSP `script-src 'self'` 兜底(冒烟断言) |
|
||
| 跨站伪造 POST(含 login/logout)被拒;Cookie 属性与 `__Host-` 前缀;刷新后 CSRF 经 /api/me 重取 | ✅ | `TestCSRF`(无 token/错 token/跨源 login/logout/缺 Origin+Referer);`TestLoginFlow`(/api/me 返回 csrf_token);实测 Set-Cookie:`__Host-pn_session=…; Path=/; Max-Age=604800; HttpOnly; Secure; SameSite=Lax` |
|
||
| 伪造扩展名/SVG/超限文件被拒 | ✅ | `TestUploadValidation`(魔数不符 415、SVG 拒绝、声明类型白名单、空文件 400、>5MB 413、同字节去重同 id) |
|
||
| 登录爆破触发 429;失败日志含 IP | ✅ | `TestLoginRateLimit`(第 6 次失败 429 + Retry-After、正确口令在桶空时同样 429、成功不计费);实测日志 `login_failed ip=127.0.0.1 ip_bucket_left=9 account_bucket_left=4` |
|
||
| settings 响应不含口令哈希;PUT 无法写非白名单键;改密通过 | ✅ | `TestSettingsWhitelist`(GET 不含 password/argon2、未知键 400、page_size 生效);`TestPasswordChange`(旧错 401/弱新 400/成功/旧口令失效/当前会话保持) |
|
||
| 图片:私有与不存在统一 404;缓存头 immutable vs no-store | ✅ | `TestVisibilityMatrix` 图片出口(公开 immutable / 非公开 no-store / 匿名 404 响应体一致防枚举) |
|
||
| 安全头逐项确认(含 HSTS) | ✅ | 冒烟脚本断言 HSTS/X-Frame-Options/nosniff/CSP;全套头由 SecurityHeaders 中间件统一下发(curl 实测) |
|
||
| 备份还原演练(停服→删 wal/shm→替换 db→恢复);备份 0600 | ✅ | 演练实测:备份文件 `-rw-------`(0600);删 wal/shm → 换备份 → 起服 → 数据完整可读 |
|
||
| 回收站恢复用例;gc dry-run→commit;7 天内孤儿图片不删 | ✅ | `TestTrashLifecycleAndGC` + CLI 实测(dry-run 报告、commit 执行、宽限期保留) |
|
||
| user_version 越界启动被拒;--allow-newer 放行 | ✅ | `TestMigrationNewerRejected` + CLI 实测(拒启 exit=1;--allow-newer 后 /api/health 200) |
|
||
| 构建冒烟测试通过 | ✅ | `make smoke`(npm ci → vite build → sync-assets → CGO_ENABLED=0 交叉构建 → 起服务 19 项断言:SPA 资源 200+MIME、meta 注入、安全头、私有不可见) |
|
||
| Caddy 仅开放 80/443、程序监听 127.0.0.1 | ✅(配置交付) | deploy/Caddyfile + pn.service(`--addr 127.0.0.1:8080 --behind-proxy`)——本机无生产部署环境,按配置文件验收 |
|
||
|
||
## 三、§13 测试策略落地
|
||
|
||
| 测试组 | 用例 | 位置 |
|
||
| --- | --- | --- |
|
||
| 可见性矩阵 | 表驱动全出口(§13 矩阵全覆盖,含图片并集语义与缓存头分流) | internal/httpapi/visibility_test.go |
|
||
| 迁移 | 空库→最新幂等;越界拒启;allow-newer 放行;外键级联 | internal/store/store_test.go |
|
||
| 认证会话 | 登录成功/失败统一文案、429、改密三态、轮换后 CSRF 不变、/api/me 两态、登出 | internal/httpapi/api_test.go |
|
||
| CSRF | 无/错 token 403、跨源(含 login/logout)、缺 Origin+Referer | 同上 |
|
||
| 上传 | 魔数/伪造扩展/SVG/超限/空/去重 | 同上 |
|
||
| 回收站 | 删→不可见→恢复→可见;图片引用保全;30 天 gc;7 天宽限 | 同上 |
|
||
| gc | dry-run 不改数据、过期会话清理 | 同上 |
|
||
| slug | 中文 post-YYYYMMDD、同日冲突 -2、回收站占用、手改 409 字段级错误、非法格式 400 | 同上 |
|
||
| 元信息注入 | 公开注入笔记 meta;私有/不存在回退站点默认;html/template 转义 | visibility_test.go + webui_test.go |
|
||
| 前端 vitest | sanitize schema 快照 + 恶意 Markdown 冒烟 + GFM 回归 + node prop 泄漏检查 | web/tests/markdown.test.tsx(6/6) |
|
||
|
||
**回归命令**:`make test`(go vet + go test ./... + vitest)与 `make smoke`。
|
||
|
||
## 四、里程碑验收门(§12)
|
||
|
||
| 里程碑 | 验收门 | 状态 |
|
||
| --- | --- | --- |
|
||
| M0 骨架 | `make build` 产出可运行二进制;serve 起服务;/api/health 200 | ✅ |
|
||
| M1 公开浏览 | 笔记/标签 API + 博客三页 + 渲染管线 + RSS/sitemap + meta 注入;公开侧可见性用例;构建冒烟 | ✅ |
|
||
| M2 管理端 | 登录/会话/CSRF + CRUD + 回收站 + 图片上传 + 编辑器 + 改密;完整矩阵 + 各组用例随功能交付 | ✅ |
|
||
| M3 安全加固 | §9.3 全项通过(见上表);CSP nonce 硬化按设计留 M3+ 待办 | ✅ |
|
||
| M4 运维 | systemd ×3 + Caddyfile + 备份恢复演练真还原 | ✅(压测 hey 未做:单机个人规模非验收必需,工具未安装;`SetMaxOpenConns(1)` 串行模型按设计实现) |
|
||
|
||
## 五、真实浏览器走查(补充验收)
|
||
|
||
以内置浏览器对 `make build` 产物做黑盒走查,全部通过:
|
||
|
||
1. **博客首页**:暗色主题、站点标题、置顶排序、标签云计数、页脚 RSS/Sitemap;
|
||
2. **详情页**:面包屑、标题/日期/标签、GFM 表格与任务列表(disabled checkbox)渲染、上一篇/下一篇;
|
||
3. **登录**:错误口令提示、成功跳转 /admin;
|
||
4. **管理列表**:公/私徽标、slug/时间/标签、状态切换/编辑/删除;
|
||
5. **编辑器**:CodeMirror 输入、分屏实时预览、保存后自动生成 slug("go")、摘要自动截取、URL 迁移到 /notes/3/edit;
|
||
6. **回收站**:删除确认弹窗 → 列表移除 → 回收站出现 → 恢复 → 公开页重新可见(标签页标题即 meta 注入验证)。
|
||
|
||
## 六、交付物清单
|
||
|
||
```
|
||
pn(单二进制,CGO_ENABLED=0,go:embed 前端产物)
|
||
├── cmd/pn/ start / init / passwd / backup / gc / version
|
||
├── internal/{config,store,auth,markdown,middleware,httpapi,webui}
|
||
├── web/ React 19 + Vite 8 + Tailwind 4(vitest 6 用例)
|
||
├── deploy/ pn.service、pn-maint.{service,timer}、Caddyfile
|
||
├── scripts/smoke.sh 构建冒烟(make smoke)
|
||
├── Makefile / README.md / .gitignore
|
||
└── docs/{design.md, review-round1.md, decisions.md, acceptance.md}
|
||
```
|
||
|
||
依赖面:Go 直接依赖 6 个(预算 ≤6);npm 生产依赖与设计一致,0 漏洞。
|
||
锁定文件 go.sum / package-lock.json 入库。
|
||
|
||
## 七、遗留事项(不阻塞验收)
|
||
|
||
1. CSP `style-src` nonce 硬化 —— 设计明示 M3+ 待办(§9.2/§14),v1 保留 'unsafe-inline';
|
||
2. hey 压测 —— M4 可选项,需生产规格环境;
|
||
3. `vite dev` 开发态标签页标题显示模板占位符(D23,仅开发态外观);
|
||
4. shadcn CLI 未使用,按「复制式组件」理念手写等价最小组件集(D22)。
|