# Tasks
# 后端 interview-koa2-backend
- [x] 生成主密钥(32 字节随机),写入
.env的PAID_DOC_SECRET - [x]
conf/secretKeys.js导出PAID_DOC_SECRET - [x]
utils/paidDoc.js新增derivePaidDocKey(documentId),用crypto.createHmac('sha256', ...);未复用utils/cryp.js(其固定全零 IV 不适用) - [x] 新增
routes/interviewClient/doc.js,router.prefix('/api/interview/doc'),GET /key - [x] 挂
checkInterviewLogin,照routes/interviewClient/video.js:92先例做if(!ctx.isVip)闸门 - [x] 按
ctx.interviewUserId维度限流(1 分钟 20 次),未依赖middlewares/interfaceLimit.js - [x] curl 实测:无 token →
code 401;错 token →code -1;合法 token + 非法 documentId →documentId 不合法;合法 token + 非会员 →会员专享 - [ ] 未验证:真实会员账号的正向返回。需要生产库里的 VIP 用户,未在无人确认时读生产数据
- [ ] 上线时同步服务器与云托管的
PAID_DOC_SECRET,并更新该仓dev-guideskill 第 4 节环境变量清单
# 仓库卫生
- [x]
.gitignore新增.vuepress/static-doc-sources/,并注明两个 generated 目录必须入库 - [x] 新增
.env.example,说明PAID_DOC_SECRET/PAID_DOC_ENCRYPT,不含真实值 - [x] 确认 140 个明文
generated-static-docs/*.json未被提交
# 生成侧
- [x]
scripts/encrypt-static-docs.mjs:AES-256-GCM 加密 + 顶层节点边界切预览段(纯文本 1200 起 / 2500 封顶) - [x] 缺失
PAID_DOC_SECRET时报错退出,不静默降级 - [x] 写盘前自校验解密结果与原文一致;
--check复验 147/147 通过 - [x]
--decrypt回退分支,输出旧形态{ html, headers } - [x] 产物拆两个目录:
generated-static-docs(headers + preview,饿加载)与generated-static-cipher(密文,按篇懒加载) - [x]
scripts/migrate-md-to-static.mjs:把本仓 7 篇大文章迁入管线,带标题数 / 尾部标题 / 文本比例三项自检 - [x]
scripts/audit-content-leak.mjs:可复跑的泄露审计,探针自动剔除与预览段重叠者
# 运行时
- [x]
theme/util/api.js新增getDocumentKey,走现有request.js实例 - [x]
theme/util/paidDoc.js:密钥缓存(内存 Map + sessionStorage,TTL 30 分钟)+ WebCrypto 解密 + 四类失败分型 - [x]
StaticInterviewDocument.vue:首帧渲染 preview、有登录态才请求密钥、密文懒加载后解密替换,兼容v缺失的旧形态 - [x] SSR 守卫:
window/crypto.subtle访问均包typeof window !== 'undefined' - [x]
Page.vue:静态文档分支不截取正文、不卸载<Content />,监听static-doc-ready重跑就绪检测 - [x] 匿名访客不打密钥接口(实测 0 次调用)
- [x] 深锚点权限状态机:匿名保留目标并拉起登录;登录成功自动续跳;401 清理失效 token/密钥;非会员滚动到权限卡,不再静默停在预览首题
# 验证结果
- [x]
vuepress build通过(Node 22 需--openssl-legacy-provider;yarn被 corepack 解析成 v4,改用./node_modules/.bin/vuepress) - [x] 加解密往返 147/147;
preview是正文前缀 - [x] Node ↔ WebCrypto 互操作实测通过(含 GCM tag 拼接),错误密钥被
OperationError拒绝 - [x] 泄露审计:882 条探针 / 53.2 MB dist 全量搜索 / 0 命中
- [x] 全站爬取模拟:248 页只能刮到 23.8 万字符(预览段 + 剩余小页面 + UI)
- [x] 进阶攻击者解析 chunk:拿到 32 万字符预览明文 + 34.5 MB 无法解读的 base64
- [x] 浏览器路径 A(未登录):静态分支生效、无骨架屏残留、可见 1609 字符、受保护片段不可见
- [x] 浏览器路径 B(会员):
fullHtml17,221 字符、可见 1609 → 9949、受保护片段可见、isDocumentReady为真 - [x] 侧边栏锚点 30 条正常(headers 明文未破)
- [x] 密文 chunk 不被任何页面 HTML 引用,确认懒加载
- [ ] 未做:移动端布局、站内连续翻页回归、Lighthouse 对比
# 遗留问题
- [ ] 单页 JS 仍 3.99 MB,其中
fe-12.js1.42 MB 是 147 份预览段的饿加载 context。可进一步把预览段下沉到各页自己的 chunk - [ ]
fe-app.js2.21 MB(siteData,241 页 frontmatter)是既有问题,与本次改动无关 - [ ] 89 个 mind-map 小页面仍是明文,合计 5.9 万字符(多为 200-300 字符的 iframe 嵌入),价值低
- [ ] 万能解锁码
_local(Layout.vue:2053,纯客户端放行且主动removeToken('fe-token'))在加密后失效,需决定是否改为服务端校验后换取 token - [ ] 本地浏览器验证必须用
localhost而非127.0.0.1:Layout.vue:973的域名白名单会把非白名单域名跳转到生产站