This commit is contained in:
2026-09-17 23:00:47 +08:00
parent def731fc5a
commit 8883a5c506
43 changed files with 4530 additions and 1515 deletions
+47
View File
@@ -0,0 +1,47 @@
# 2026-09-17
## 构建管线建立(第二项遗留问题)
- 引入 esbuild 0.28.2(devDependency),tsconfig 改 `noEmit: true`:tsc 只做类型检查,esbuild 产出 dist/。
- 新增 `scripts/build.mjs`(6 入口:utils/env-store/crypto-store/background/content 不打包 + popup/main ESM 打包)、`scripts/package.mjs`(zip 打包)、`scripts/release.mjs`(版本递增 + 同步三处版本 + 打包)、`.gitea/workflows/release.yml`(tag 触发 CI)。
- 踩坑:esbuild 只要指定 `format: "iife"` 就会把 UMD 的 `module.exports` 换成合成对象(`__commonJS` 包装),导致测试 require 拿到 `{}`;**去掉 format 即可**(bundle:false + 不指定 format 输出正确)。
- dist/ 已 `git rm --cached` 停止跟踪并加入 .gitignore;release/、*.zip、*.crx、*.pem 一并忽略。
- 顺带修掉历史遗留失败:`tests/detect-captcha.test.js` 的 chrome mock 缺 `runtime.sendMessage`/`runtime.id`,导致 content.js 加载时自动填充抛未处理拒绝。现在 **54/54 全绿**。
- 验证:dev/release 两种构建下测试均通过;`npm run release -- patch` 端到端跑通(zip 生成、版本三处同步、仅改 version 行不动格式),验证后已回滚到 1.0.0。
## 安全债两条的实施(用户要求:先方案 B,再解决注入问题)
- 方案 B:新增 `src/key-store.ts`(UMD,IndexedDB 存取不可导出 CryptoKey 句柄);`crypto-store.ts` 新增 `unlockWithKey(key)` 并让 `unlock` 复用它;`background.ts` 彻底去掉 `__autoPwd`(仅保留一次性迁移分支),`setupVault`/`unlock` 时 `KeyStore.saveKey`,`doLock` 时 `KeyStore.deleteKey`。
- 注入收敛:`manifest.json` 删除静态 `content_scripts`;`utils.ts` 新增 `toMatchPatterns()`(去协议/端口/IDN→punycode,path 截断到第一个 `*`);`background.ts` 新增 `syncContentScripts()`,在 onInstalled/onStartup/SW 启动/storage.onChanged/setupVault/unlock 时按已配置域名动态注册,锁定时自动清空;`content.ts` 顶部加 `window.__autoLoginInjected` 标记,popup `fill-current.ts` 改为"探测后再 executeScript 注入"。
- 构建:`build.mjs` 的 GLOBAL_SCRIPTS 加入 `key-store`,并新增 INJECTED_ASSETS 校验(manifest 里已不再声明 content.js/utils.js,仍需存在)。
- 测试:54 → **75 全绿**。新增 tests/key-store.test.js(含极简 IndexedDB shim)、utils 的 toMatchPatterns 用例、crypto-store 的 unlockWithKey 用例(含"重设口令后旧句柄失效")。
- 待办(用户未要求):`host_permissions: <all_urls>` 换成 optional_host_permissions 按域名申请,以消除安装时的全站数据警告。
## 补齐铂金→钻石的最后两项(用户要求:先解决第一项、第二项)
- **content.ts 测试**:引入 jsdom(devDependency,Node 24 环境),新增 `tests/content-fill.test.js`(约 18KB / 24 用例)覆盖字段探测(含 Shadow DOM 穿透、HTML 片段定位、隐藏元素跳过)、填充事件、自动提交延迟、验证码拦截、requestSubmit 回退、自动填充主流程、失败次数上限与冷却、多环境浮窗选择/取消、SPA 延迟重试、manualFill/manualFillMulti 消息。测试总数 75 → **99 全绿**。
- 技巧:用 `runScripts: "dangerously"` + 注入 script 标签执行 utils.js/content.js(与浏览器经典脚本语义一致,函数挂到 window);jsdom 无布局引擎 → 原型上覆写 offsetWidth/offsetParent 让元素"默认可见",用 hide() 单独模拟隐藏;jsdom 缺 CSS.escape 需 polyfill;跨 realm 的响应对象不能用 deepEqual(原型不同),改用字段断言。
- **合规文档**:新增 `README.md`(功能/安全设计/技术栈/目录结构/命令/本地加载/发布流程/权限表)、`PRIVACY.md`(中英双语,说明不收集数据、三类存储位置、加密参数、权限用途、导出删除)、`CHANGELOG.md`(Keep a Changelog,Unreleased + 1.0.0=2026-09-14)、`LICENSE`(**MIT,作者 chenxuan,用户未指定许可,已提示可改**)。
- 事实核对:vault PBKDF2 20 万次、导出文件 10 万次、SPA 重试 30s、失败 3 次/冷却 5 分钟、自动锁定 10 分钟、首次提交 2026-09-14、共 30 次提交。
- 扩展 zip 仍只含运行时文件(22 个文件),文档与源码不入包。
## 段位复评(改造后)
- 结论:**铂金(稳固)**,构建发布与安全设计摸到钻石边,被文档合规与测试深度拖住。
- 量化现状:源码 3531 行(content.ts 612 / background.ts 341 / 最大 popup 模块 config-list 238);测试 920 行 75 用例全绿;压缩后产物合计 59KB(dev 97KB);双语 830 行文案。
- 距钻石的差距(按阻塞程度):① 无 README/CHANGELOG/LICENSE/隐私政策(CWS 处理凭据需隐私政策,唯一硬阻塞)② content.ts 612 行最复杂逻辑无测试 ③ host_permissions <all_urls> 未收敛 ④ 无 lint/formatter 配置 ⑤ deepQuerySelectorAll 每 500ms 全量遍历+扫描 shadowRoot,最长 30s ⑥ 调试日志无开关 ⑦ activeTab 权限已冗余。
- 提醒用户:改造完成时有 32 项改动未提交,建议先形成基线。
- 用户要求解释「__autoPwd 明文口令」与「<all_urls> 静态注入」两条。已给出完整讲解与改造方案,结论与核实要点记入 MEMORY.md。
- 重要更正:早前称"任何 content script 都能读到 storage.session"有误 —— 官方文档确认 storage.session 默认仅受信任上下文可访问,需 setAccessLevel 才放开。攻击面论述已改为"安全等级降级 + 口令可被抄走"这一更准确的表述。
- 已核实 CryptoKey 可 structured clone 入 IndexedDB 且保持 extractable:false,这是替代 __autoPwd 的推荐实现路径。
- 用户尚未开始动手改这两条,等待其决定。
## popup.ts 1479 行巨石拆分完成
- 拆为 src/popup/ 下 15 个模块:dom/toast/toggle-pwd/context(基础)、pwd-modal/env-modal/config-edit-modal(弹窗)、env-section/config-form/config-list(业务)、transfer/bookmark-import/fill-current/lock(功能)、main(入口)。
- 关键设计:模块间不互相 import,统一通过 context.ts 的 PopupCtx 接口互调(showToast/resetForm/renderEnvSelect/renderConfigList/refreshEnvManage/openEditModal 等),由 main.ts 组装,避免循环依赖。pwd-modal/env-modal 用 Promise resolve 模式供 transfer 等模块 await。
- popup.html 入口改为 dist/popup/main.js(type=module),旧 dist/popup.js 已删除。EnvStore/isDomainMatch/escapeHtml 为全局变量(globals.d.ts 声明),模块内不可 import。
- 编译通过;npm test 54/55 通过,detect-captcha.test.js 的 "_sendMessage is not a function" 失败为拆分前已存在问题(git stash 验证过),与本次无关。
## auto-login 浏览器插件段位评估
- 项目评级:黄金→冲击铂金(业余高分段,准专业)。
- 优点:PBKDF2(200k)+AES-GCM 整库加密、明文只进 storage.session、MV3 适配(SW 重启 autoRestoreUnlock/sidePanel)、Shadow DOM 穿透 + setNativeValue 的表单引擎、验证码检测、失败锁定、i18n、有 TS + node --test 测试。
- 主要问题:① background.ts 把主口令明文存 chrome.storage.session 的 `__autoPwd` 实现 SW 重启自动解锁——安全模型自我瓦解,最需修复;② popup.ts 1479 行巨石未拆分;③ content script 注入 <all_urls> + body MutationObserver,建议改 scripting.executeScript 按需注入;④ 纯 tsc 无构建管线、dist 入库;⑤ content.ts 无测试。
+31
View File
@@ -0,0 +1,31 @@
# auto-login 项目长期记忆
## 已知安全债(2026-09-17 已修复前两条)
1. **已修复**:原实现把主口令明文存 `chrome.storage.session.__autoPwd`。现改为**把 non-extractable CryptoKey 句柄存入 IndexedDB**(`src/key-store.ts`),SW 重启后用 `CryptoStore.unlockWithKey(key)` 恢复解锁,口令不落任何存储;`doLock()` 会删除句柄。旧版 `__autoPwd` 保留一次性迁移分支(读到即转句柄并删除明文)。
- 已核实:`storage.session` 默认**不向 content script 开放**(仅受信任上下文)——早前"任何 content script 都能读到"的说法是错的。
- 已核实:CryptoKey 支持 structured clone 存入 IndexedDB,取回后 extractable 仍为 false(exportKey 抛错)但可正常 decrypt。
2. **已修复**:原 manifest 静态 `content_scripts` + `<all_urls>` 无条件注入。现移除静态声明,background 通过 `chrome.scripting.registerContentScripts` 只注册**用户配置过的域名**(`syncContentScripts`,由 `utils.toMatchPatterns` 生成 match pattern);popup 手动填充改为"探测 `window.__autoLoginInjected` → `chrome.scripting.executeScript` 按需注入"。
- 仍保留 `host_permissions: ["<all_urls>"]`(按需注入需要),若要进一步收窄安装提示可改 `optional_host_permissions` 并按域名申请。
- 锁定时 session 无明文域名 → 注入范围自动清空;解锁后才重新注册。
## 项目性质
Chrome MV3「Auto Login Manager」扩展(域名配置自动填充 + 加密存储 + 多环境)。自建 Gitea 仓库:`http://218.61.196.156:41695/Chrome-extension/auto-login.git`。
## 环境注意事项
- npm 全局缓存 `~/.npm` 有权限问题,`npm install` 必须加 `--cache /tmp/npm-cache-autologin`。
- 全局安装不可用(权限),依赖一律装为 devDependency。
## 构建约定(2026-09-17 建立)
- 类型检查与产物生成分离:`tsc --noEmit` 只做类型检查(tsconfig 已设 `noEmit: true`),**esbuild 负责产出 dist/**。
- 产物分三类,不可混为一谈:
1. UMD 全局脚本 `utils/env-store/crypto-store`:**必须不打包、不指定 format**。一旦指定 iife,esbuild 会把 `module.exports` 换成合成模块对象,Node 测试 `require(dist/xxx.js)` 就取不到导出。
2. 独立入口 `background/content`:不打包(运行期靠 `importScripts` 与全局变量协作)。
3. `popup/main.ts`:唯一真正打包的入口(ESM,静态依赖全部内联,由 popup.html 以 `type="module"` 加载)。
- `scripts/build.mjs` 会校验 manifest.json / popup.html 引用的文件是否都存在(11 处),并检查 manifest 与 package.json 版本一致性。
- dist/ 已从 git 移除(`.gitignore`),克隆后必须先 `npm run build` 才能加载扩展。
- 测试依赖 dist:`npm test` = build + `node --test tests/*.test.js`;只想跑测试用 `npm run test:only`(需已有 dist)。
- 发布:`npm run release -- patch|minor|major|x.y.z [--codebase <url>]` → 同步 manifest.json / package.json / updates.xml(只替换 version 行,保留原格式)→ 发布构建 → `release/auto-login-v<ver>.zip`。不自动 commit/tag。
## 部署产物
- `updates.xml`:自托管自更新清单,`appid=dpcmpkmjkcenjpfcmkoohkcnhbomgmej`,codebase 指向 Windows 本地路径的 .crx。
- `install-policy.reg`:Windows 强制安装策略。两者都不打进扩展 zip。