Releases: luck2026228/DeclineLens
Release list
v3.1.3
Stripe 拒付原因翻译器。把 Your card was declined. 这一句话,还原成银行到底说了什么、以及该怎么办。
两种装法,功能一样,挑一个:
| 怎么装 | |
|---|---|
| 油猴脚本(推荐,最省事) | 装好 Tampermonkey 后点这个链接:DeclineLens.user.js |
| Chrome / Edge 扩展 | 下面下载 DeclineLens-v3.1.3-chrome.zip,解压,浏览器地址栏进 chrome://extensions,打开右上角「开发者模式」,点「加载已解压的扩展程序」,选解压出来的文件夹 |
| Firefox 扩展 | 下面下载 DeclineLens-v3.1.3-firefox.zip,进 about:debugging → 「此 Firefox」→「临时载入附加组件」 |
它不做什么:不发任何网络请求,不上传任何数据,全部记录只存在你自己浏览器里,随时一键清空。详见 PRIVACY.md。
这一版改了什么
修复
-
Firefox 的「官方 MAIN 世界通道」以前是死代码,现在真的走通了。
content.js里的registerMainWorld()在内容脚本里调用browser.scripting.registerContentScripts,
但scripting这套 API 在内容脚本里根本不存在(内容脚本只拿得到storage/runtime/i18n那一小撮),
browser.scripting恒为undefined,函数每次都在第一句就return—— v3.1.2 补的host_permissions
也救不了它。后果是 Firefox 上只剩script标签注入这一条路,而它会被页面 CSP 挡掉,
checkout.stripe.com自己就是严格 CSP,恰恰是最该抓到的那个页面抓不到。
改法:manifest.firefox.json的content_scripts里直接给pagehook.js声明"world": "MAIN"
(Firefox 128 起原生支持)。registerMainWorld()整段删除,scripting权限一并去掉。
Firefox 127 及以下不认识world这个 key,会把pagehook.js塞进隔离世界 —— 那样它会插上握手旗子、
让script标签兜底不再执行,等于把老版本钩废;所以pagehook.js开头加了一句世界判定
(隔离世界里browser.runtime.id有值,主世界里没有),认出来直接退出,照旧落回兜底路径。 -
XHR 对象被复用时,监听器会一直堆积。
pagehook.js每次send()命中白名单就addEventListener("load", ...),从不摘除。
同一个 XHR 复用 N 次就挂 N 个监听器,load时全部触发:一个响应被记 N 条;
更糟的是,命中支付接口留下的监听器,会在后续不命中的请求load时照样触发,
去读白名单之外的响应体 —— 直接破掉「不命中的请求,响应体连碰都不碰」这条自己写的隐私边界。
改法:每次send()打一个自增序号,闭包捕获,监听器里序号对不上立刻return;
监听器改成{ once: true },触发完自动摘除。序号是每次 send 都加,不是只在命中时加 ——
否则「命中的请求被中断、同一个对象接着发一个不命中的请求」时序号仍然对得上,漏洞照旧。
油猴版有同样的代码,一并改了。 -
跨 frame 的写入竞态。
content.js是all_frames: true,顶层页面和每个 iframe 各跑一份实例;
里面那条writeQueue只在自己这份实例内排队,跨 frame 完全不排队,
而chrome.storage.local是所有 frame 共用的同一份 —— 两个 frame 同时get→改→set,
后写的那次拿到旧数组,会把另一条记录整个盖掉。Stripe Elements 的确认请求恰恰是从 iframe 里发出去的,
正是最容易撞车的场景。
改法:子 frame 不再自己落盘,把记录postMessage给顶层,由顶层那一份实例排队统一写。 -
/v1/tokens、/v1/charges这几条泛路径会误抓非支付站点。
任何站点自家的/api/v1/tokens(比如登录换 token)都会命中白名单,失败响应里的error.code
被记成一条「拒付」,弹窗里冒出莫名其妙的「未收录的原因码」。
改法:URL 不是stripe.com域名、只靠泛路径命中时,追加一道校验 ——error.type必须是 Stripe
的六种官方类型之一,或者响应里有 Stripe 特有的outcome/object结构,否则不落盘。
PAY_URL那一行没动(两个版本必须逐字一致,test.js会查)。 -
Chrome 版的兜底注入以前是死路。
content.js用<script src="chrome-extension://...">加载pagehook.js,
但 Chrome 清单里没有web_accessible_resources,MV3 下这条路必然加载失败,
失败后<script>元素还留在页面 DOM 里(只写了onload清理、没写onerror)。
实际影响很小(Chrome 走清单的 MAIN 世界很可靠,兜底路平时不执行),但补上了:
Chrome 清单加web_accessible_resources,注入代码加onerror清理。
测试
- 自检从 60 项加到 71 项:泛路径闸门 2 条、XHR 复用 3 条、结构性检查 6 条。
v3.1.2
Stripe 拒付原因翻译器。把 Your card was declined. 这一句话,还原成银行到底说了什么、以及该怎么办。
两种装法,功能一样,挑一个:
| 怎么装 | |
|---|---|
| 油猴脚本(推荐,最省事) | 装好 Tampermonkey 后点这个链接:DeclineLens.user.js |
| Chrome / Edge 扩展 | 下面下载 DeclineLens-v3.1.2-chrome.zip,解压,浏览器地址栏进 chrome://extensions,打开右上角「开发者模式」,点「加载已解压的扩展程序」,选解压出来的文件夹 |
| Firefox 扩展 | 下面下载 DeclineLens-v3.1.2-firefox.zip,进 about:debugging → 「此 Firefox」→「临时载入附加组件」 |
它不做什么:不发任何网络请求,不上传任何数据,全部记录只存在你自己浏览器里,随时一键清空。详见 PRIVACY.md。
这一版改了什么
修复
- 油猴版现在也会在 DOM 上插握手旗子
data-declinelens。
之前只设了页面window上的标记。两个版本同时安装、且油猴版抢先跑到时,插件版的content.js看不到任何标记,3 秒后就会往hookFail里写一条 —— 弹窗顶部弹出「CSP 拦住了注入」的黄条,而实际上一切正常。属于虚惊一场型的误报,现在消除了。 - Firefox 清单补上
host_permissions: ["http://*/*", "https://*/*"]。
browser.scripting.registerContentScripts除了scripting权限,还要求扩展持有目标页面的主机权限,否则注入的脚本不会运行。缺这一条的后果是:文档里写的「Firefox 128+ 走官方 MAIN 世界通道」这条正路从来没有真正启动过,每次都静默退化成script标签注入 —— 而那条路会被有 CSP 的站点挡掉。
注意 Firefox MV3 里host_permissions默认是可选权限,用户仍需在about:addons里手动授权,README 4.3 节已写明这一步。
测试
- 自检从 58 项加到 60 项,新增两条专盯上面第一个问题:油猴版必须插旗子、
content.js必须认同一面旗子。