# Tasks

# 后端 interview-koa2-backend

  • [x] 生成主密钥(32 字节随机),写入 .envPAID_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.jsrouter.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-guide skill 第 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-provideryarn 被 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(会员):fullHtml 17,221 字符、可见 1609 → 9949、受保护片段可见、isDocumentReady 为真
  • [x] 侧边栏锚点 30 条正常(headers 明文未破)
  • [x] 密文 chunk 不被任何页面 HTML 引用,确认懒加载
  • [ ] 未做:移动端布局、站内连续翻页回归、Lighthouse 对比

# 遗留问题

  • [ ] 单页 JS 仍 3.99 MB,其中 fe-12.js 1.42 MB 是 147 份预览段的饿加载 context。可进一步把预览段下沉到各页自己的 chunk
  • [ ] fe-app.js 2.21 MB(siteData,241 页 frontmatter)是既有问题,与本次改动无关
  • [ ] 89 个 mind-map 小页面仍是明文,合计 5.9 万字符(多为 200-300 字符的 iframe 嵌入),价值低
  • [ ] 万能解锁码 _localLayout.vue:2053,纯客户端放行且主动 removeToken('fe-token'))在加密后失效,需决定是否改为服务端校验后换取 token
  • [ ] 本地浏览器验证必须用 localhost 而非 127.0.0.1Layout.vue:973 的域名白名单会把非白名单域名跳转到生产站
webapp
公众号
开发者导航
切换夜间模式
折叠侧边栏
收起全部