Files
auto-login/.codebuddy/memory/2026-09-17.md
T
陈银军 1b0c3c012c feat: 按域名授权、iframe 多帧填充、自动提交限流修正,并补齐工程规范与文档
安全与权限:
- 站点访问权限改为 optional_host_permissions,保存配置时逐域名授权;移除 host_permissions 与 tabs,安装提示不再出现全站数据访问与浏览记录
- 主口令不再写入 storage.session,改用 IndexedDB 中不可导出的 CryptoKey 句柄恢复解锁,锁定即丢弃
- content script 注入范围只注册已授权域名,并随权限变化即时收敛

自动登录:
- 支持 iframe 内的登录表单:脚本注入所有帧,逐帧按自身地址匹配配置
- 手动填充改为逐帧探测,定向发送到真正含密码框的帧
- 限流修正:只在真正触发提交后计数;判定登录成功后立即清零;达到上限时页面给出可见提示;重置判断只看启用中的配置
- SPA 重试改为 DOM 变更门控 + 退避,并单独监听已发现的 Shadow Root

工程化与文档:
- 引入 ESLint(扁平配置)与 Prettier,CI 增加 lint 与 format:check
- 信息类日志改为 debugLog(默认静默,chrome.storage.local.debugLog 开关)
- 测试 123 → 128 用例(新增权限、iframe、限流相关用例)
- README / PRIVACY / CHANGELOG 同步
2026-09-18 00:05:20 +08:00

71 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 个文件),文档与源码不入包。
## 采用方案 C:站点权限改为按域名可选授权
- 新增 `src/permissions.ts`(UMD 全局 `Perms`)+ `tests/permissions.test.js`(13 用例)→ 测试 **112 全绿**。
- manifest:去掉 `host_permissions: <all_urls>` 与 `tabs`,改 `optional_host_permissions: ["*://*/*"]`;popup.html 增加 `dist/permissions.js` 引用;build 的 GLOBAL_SCRIPTS 加 `permissions`(现 8 入口、12 处引用校验)。
- background:只注册已授权 pattern;新增 `chrome.permissions.onAdded/onRemoved` 监听;抽出 `loadEnvironmentsForMatching` / `collectConfigDomains`;新增**兜底注入**(注册失败 → onUpdated 时 executeScript)。
- popup:新增/编辑保存时先 `Perms.requestForDomain`(紧跟手势);失败提示 toastUnauthorized;配置列表加"未授权"标签可点击授权;`fill-current` 与两个"填入当前域名"按钮改用 activeTab 并区分提示(toastNoTabUrl / toastNeedActiveTab);ui-utils 新增 `resolveAlias` 并修正 `tabs.query({url})` 过滤失效的隐患。
- i18n:中英各新增 7 个键;popup.css 新增 `.tag-unauthorized`。
- 文档同步:README 安全设计与权限表、PRIVACY 双语权限段、CHANGELOG(Changed/Security)。
- 待真机验证(我无法在此环境跑 Chrome):① 安装提示是否已无全站/浏览记录警告 ② 保存配置时是否弹授权框 ③ 授权后自动填充是否生效、`registerContentScripts` 是否接受运行时授权(若失败看 SW 控制台是否出现"降级为按需注入")④ 升级后旧配置需逐个点"未授权"标签授权。
## 收尾四项打磨(用户要求,其中第 4 项已过期)
- **第 4 项「activeTab 冗余」已失效**:C 方案移除了 host_permissions 与 tabs 之后,activeTab 变成手动填充/读 tab.url 的**必要**权限,不能删。已向用户说明。
- lint/formatter:新增 `eslint.config.mjs` + `.prettierrc.json` + `.prettierignore`,devDeps 加 eslint/@eslint/js/typescript-eslint/prettier/globals(安装需 `--cache /tmp/npm-cache-autologin`)。修复了真实问题:main.ts 未使用的 `t` 导入、prefer-const(改用 api 容器 + 非空断言重构初始化顺序)、content.ts 三处"表达式语句"(requestSubmit 三元、两处 `overlay.parentNode && ...` → 抽出 `closeEnvPicker()`)、package.mjs 缺 `cause`、测试未使用参数。CI 增加 lint + format:check。
- 调试日志:utils.ts 新增 `initDebugLog/isDebugLogEnabled/debugLog`,开关 `chrome.storage.local.debugLog`;content.ts/background.ts 的信息类日志全部改为 debugLog,异常类保留 warn;background 与 content 启动时先 init 再执行主流程。
- 性能:SPA 重试改为"DOM 变更门控 + 退避"(`domVersion` + `SPA_RETRY_DELAYS_MS` 300→3000ms),30 秒窗口内尝试次数 60 → 14 且多数轮次跳过扫描;**关键修正**:body 上的 MutationObserver 看不到 Shadow DOM 内部变化,若只做版本门控会漏填组件内部后渲染的登录框 → 新增 `trackShadowRoot()`,对扫描中发现的 shadow root 单独挂 observer 递增 domVersion(WeakSet 去重)。
- 测试 112 → **114 全绿**(新增 Shadow DOM 内部渲染用例、debugLog 开关用例)。lint 0 问题、format:check 通过、打包正常。
- 结论:**铂金(稳固)**,构建发布与安全设计摸到钻石边,被文档合规与测试深度拖住。
- 量化现状:源码 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 的推荐实现路径。
- 用户尚未开始动手改这两条,等待其决定。
## iframe 支持(用户反馈"iframe 里的表单填不了")
- 根因:三处注入都没开 `allFrames`(动态注册 / 兜底注入 / popup 探测与注入),默认只作用于顶层帧。
- 第一步:注册与注入全部加 `allFrames: true`;顺带修掉"兜底注入早于 body 就绪"时 `observer.observe(document.body)` 会抛错的问题(改为等 DOMContentLoaded)。
- 第二步:`isTopFrame()` 门控浮窗(子帧不弹,避免被 iframe 裁切)→ 子帧用 `pickConfigForFrame(matches, activeEnvId)` 按激活环境选择;`loadAllConfigs` 响应新增 `activeEnvId`;popup 改为逐帧探测 + `frameIds` 精确补注入 + `{frameId}` 定向发消息(修掉"iframe 填成功却收到别帧失败诊断"的错报)。
- 坑:onMessage 监听器不能改成 async(`return true` 变成微任务会让 Chrome 提前关闭通道),子帧异步填充包在内部 IIFE。
- 测试:123 全绿(新增 4 个 iframe 用例 + 5 个 `pickFrameWithForm` 用例)。**测试 harness 注意**:jsdom 每个 realm 有自己的 HTMLElement.prototype,iframe 必须单独 `patchLayout(iwin)`,否则 `offsetParent` 为 null 导致用户名框被判为不可见(真实浏览器无此问题)。
- 文档已同步 README/PRIVACY(披露"已授权域名下注入所有帧,各帧按自身地址匹配")与 CHANGELOG。
## 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 无测试。