Files
陈银军 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

12 KiB
Raw Permalink Blame History

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 无测试。