每个 Chrome 扩展都会带着一个 manifest.json,里面写清了它在浏览器层面的能力边界:请求了哪些 API、匹配哪些主机、静态内容脚本怎么注入、有哪些可选权限,以及允许哪些外部连接。这个文件会原封不动地打包进你安装的扩展里。它不能证明运行时代码到底用了哪些已声明能力、哪些数据离开了本机,或者哪些账号访问是通过单独的网页登录完成的。
我审阅了这次评估挑出来的 10 款抓取和浏览器自动化扩展,其中也包括 Thunderbit——因为这是我们的产品。它们差异非常大:有的完全不声明持续站点访问;有的声明了 13 项权限,还包括 clipboardRead。Thunderbit 是这组里唯一声明了 debugger 的扩展,这是一种比较广的 CDP 连接能力,风险形态和页面访问、OAuth 或用户自定义脚本都不一样。
这些都不是指控。广泛权限往往是实现某项功能的唯一诚实方式,而权限很少,也可能只是产品做得更少。关键在于,这种差异非常大,而且是公开可查的,但它从来不会出现在对比表里。
这项分析是怎么做的
每个扩展都从 Google 自己的更新端点下载成 .crx 文件——也就是 Chrome 自己用的同一个地址——然后解包并解析。没有安装任何扩展,也没有执行任何扩展代码。 这只是对一个 JSON 文件的读取。
下载节奏控制在大约每两秒一次请求。每个 .crx、它的 SHA-256,以及提取出来的 manifest.json 都作为证据保留。Thunderbit 和其他 9 个扩展走的是同一套脚本,没有走特殊通道,所以它的结果和其他扩展完全按同样方式得出。
分析分为两类证据,并把它们严格分开:
- 已声明的静态行为: 主机匹配规则和
content_scripts条目,包括matches、run_at和all_frames。 - 运行时代码可获得的能力:
permissions或optional_permissions中列出的 API。这些声明只说明代码可以请求或调用什么,不代表它 بالفعل 真的这么做。
这里不评“谁最强”,也不做排序。debugger、userScripts、广泛主机访问、OAuth 范围、剪贴板访问和外部消息能力,暴露的数据类型不同,前置条件也不同。要比较它们,需要一个这次仅靠 manifest 无法提供的威胁模型。
下面的体积均为解包后的总大小,单位是 MiB(2²⁰ 字节),按 ZIP 条目汇总。
截至 2026-07-29。 扩展会更新;引用前请先复查。
10 个 manifest 都写了什么

官方参考:Chrome 权限声明指南。
| 扩展 | 版本 | 解包大小 | 文件数 | 权限数 | 站点访问 | 是否可访问 file:// |
|---|---|---|---|---|---|---|
| Axiom.ai | 5.1.0 | 37.1 MiB | 232 | 8 | http://*/* + https://*/* | ✅ |
| Table Capture | 11.0.41 | 21.1 MiB | 115 | 4 (+3 个可选) | <all_urls> | ✅ |
| Magical | 3.119.1 | 16.8 MiB | 395 | 13(+2 个可选) | <all_urls> | ✅ |
| Thunderbit(我们的) | 4.6.4 | 15.9 MiB | 78 | 8 个被识别权限(+1 个未识别数组字符串:commands) | <all_urls> | ✅ |
| Clay for Chrome | 1.0.0 | 6.3 MiB | 51 | 6 | *://*/*(只在自有域名注入) | — |
| Listly | 0.9.6 | 3.5 MiB | 84 | 7 | http://*/*、https://*/*、file:///*.html | ✅ |
| Hexomatic | 1.8.4 | 2.7 MiB | 37 | 2 | 仅限自有域名 | — |
| Agenty | 2.9.7 | 2.2 MiB | 49 | 4 | 未声明 | — |
| Clip to Clay | 1.8.0 | 0.7 MiB | 16 | 4 | 2 个指定域名 | — |
| TexAu v2 | 1.6.6 | 0.3 MiB | 12 | 6 | 14 个指定域名 | — |
还有两个候选项也在名单里,但没有放进表格,原因值得单独说明。
差异来自设计选择,不是体积决定的
Agenty 没有声明任何 host 权限,也没有任何内容脚本。 它的 4 项权限分别是 activeTab、scripting、identity 和 identity.email。activeTab 是最窄的那种:只有在你点击扩展后,它才会获得当前标签页的访问权,而且只在你跳转前有效。除非你主动触发,否则页面上不会运行任何代码。它的体积只有 2.2 MiB。
Axiom.ai 声明了 http://*/* 和 https://*/*,并在匹配 <all_urls> 的页面上注入内容脚本,解包后大小为 37.1 MiB,共 232 个文件——是 Agenty 的 17 倍,而且对你访问的每一个页面都保有持续访问权。
Hexomatic 和 Agenty 更接近:它只有 2 项权限(storage、tabs),没有 host 权限,内容脚本也只限制在自己两个域名上。
Clay for Chrome 是另一种值得区分的形态:它把 *://*/* 作为 host 权限声明出来,但只在自有域名上注入内容脚本。它的持续能力很宽,但自动执行的行为很窄。只看权限表会把这两者混为一谈。
10 个里有 5 个能访问你硬盘上的文件
file:/// 不是网站,而是你本地文件系统在浏览器标签页里的呈现——比如你打开的 PDF、导出的 HTML、下载的发票。
官方参考:Chrome 匹配模式文档。
有 3 个扩展明确写了这一点。还有 2 个是“绕开命名”的:<all_urls> 本身就包含 file: 协议。
| 扩展 | 它如何访问 file:// | manifest 里是否直接写了 file://? | 位置 |
|---|---|---|---|
| Magical | 在 6 条内容脚本规则里,有 4 条匹配 file:///*——Chrome 会渲染的所有本地文件,不只是 HTML | ✅ | 内容脚本,以及 web_accessible_resources |
| Listly | 匹配 file:///*.html | ✅ | 内容脚本 |
| Table Capture | 通过 <all_urls> 内容脚本,同时也在 manifest 中直接写了该协议 | ✅ | web_accessible_resources |
| Axiom.ai | 仅凭 <all_urls> 通配符 | — | 无处不在 |
| Thunderbit(我们的) | 仅凭 <all_urls> 通配符 | — | 无处不在 |
http://*/* 和 https://*/* 并不覆盖 file://;<all_urls> 和 *://*/* 在这一点上也不同。这里的 5 个,是那些通过某种路径触达该协议的扩展——并不是因为它们用了哪种通配符形式。
Chrome 会把这件事放在每个扩展单独的“允许访问文件网址”开关后面,这个开关默认是关闭的,所以这里的声明只是请求,不是授权。10 个里有 5 个符合这个条件,且这 5 个里有 2 个从不在 manifest 中写 file://:Axiom.ai 和我们的扩展。
这个统计还包括 web_accessible_resources,不只是内容脚本和 host 权限。Table Capture 在这里明确写了 file://*/*;如果不看这一块,就会误把它算成“能访问文件却没写文件协议”的扩展之一。
只有在请求时才看得见的权限

optional_permissions 是先声明、后运行时请求的,所以它们不会出现在安装时的提示里。这里有两个扩展用了它们,其中一个很重要。
| 扩展 | optional_permissions | 其中最关键的 |
|---|---|---|
| Table Capture | userScripts、downloads、identity | userScripts 在启用 Chrome 的用户门控后,可以在页面上下文中运行用户提供的脚本 |
| Magical | downloads、webRequest | webRequest 可观察网络流量 |
userScripts 在被请求之前是看不到的,单看权限数量的人也会直接漏掉它。
而且它还有一个本次评估里其他权限没有的门槛;如果不提这一点,就会把它说得比实际更宽。声明 userScripts 并不等于能用:Chrome 要求先有明确的用户动作。 在 Chrome 138 之前,这个门槛是全局开启的开发者模式,位置在 chrome://extensions。从 Chrome 138 开始,它变成扩展详情页里的一个按扩展分别控制的 Allow User Scripts 开关,默认关闭。所以对普通安装来说,这项能力是“已声明但不生效”的。更准确的说法是:在用户打开 Chrome 的门控后,Table Capture 才能启用 userScripts API。本次评估没有测量有多少用户真的会去打开这个设置。
这两个都不是隐藏项;它们都写在 manifest 里。忽略 optional 部分的表格,会低估这两个产品。
注入时机,不只是范围
内容脚本“怎么注入”也很重要,但常被忽略,而这会改变结论。document_start 是 Chrome 提供的最早钩子;all_frames 会覆盖第三方嵌入内容。
下面是这组扩展里,所有静态注入到全部 frame 的项目,按声明时机排序:
| 扩展 | run_at | all_frames | 内容脚本匹配模式 |
|---|---|---|---|
| Table Capture | document_start | ✅ | <all_urls> |
| Listly | document_start | ✅ | file:///*.html,以及所有 http 和 https 页面 |
| Axiom.ai | document_start | ✅ | 仅自有域名 |
| Clay for Chrome | document_start | ✅ | 仅自有域名 |
| Thunderbit(我们的) | document_end | ✅ | <all_urls> |
| Magical | document_idle | ✅ | file:///* 加上广泛的 http 和 https |
| TexAu | document_idle(未显式设置) | ✅ | 14 个命名模式 |
Table Capture 和 Listly 都是在最早的钩子上、配合更广泛的匹配模式运行。Axiom.ai 和 Clay for Chrome 也用了同样的时机,但只针对自有域名——同样激进,但目标更窄。
对于静态声明来说,<all_urls> 加上 all_frames 就是匹配范围的上限,而 Table Capture 和 Thunderbit 都到达了这一层。它们声明的时机不同:Table Capture 用的是 document_start;Thunderbit 的静态内容脚本用的是 document_end。运行时 API 是另一类能力,不能从这张表里推出来。
单看权限数量是个很差的总结指标。 Table Capture 只声明了 4 项权限,比这里大多数都少,但它却在 <all_urls> 上以 document_start 注入到所有 frame,同时还可按需启用 userScripts。
谁可以给扩展发消息
externally_connectable 控制哪些网页或其他扩展可以直接向扩展的后台脚本发消息。它的默认值很反直觉。
不写这个键,反而是更宽松的设置。 Chrome 的默认规则是:当 externally_connectable 缺失时,任何扩展都可以连接,但网页不行。只有把它写出来,才是在“收窄”权限。
按这个理解,表格会反过来:
| 扩展 | externally_connectable 声明 | 谁可以给它发消息 |
|---|---|---|
| Agenty | 未声明 | 宽松默认值——任何扩展都可以连接,网页不行 |
| Clay for Chrome | 未声明 | 宽松默认值——任何扩展都可以连接,网页不行 |
| Clip to Clay | 未声明 | 宽松默认值——任何扩展都可以连接,网页不行 |
| Listly | 未声明 | 宽松默认值——任何扩展都可以连接,网页不行 |
| Table Capture | 未声明 | 宽松默认值——任何扩展都可以连接,网页不行 |
| TexAu | 未声明 | 宽松默认值——任何扩展都可以连接,网页不行 |
| Thunderbit(我们的) | 未声明 | 宽松默认值——任何扩展都可以连接,网页不行 |
| Hexomatic | 8 个扩展 ID 加上 6 个网页来源 | 从封闭基线打开了两个通道 |
| Axiom.ai | 7 个网页来源,没有 ids | 扩展通道关闭,开放了 7 个网页 |
| Magical | {"ids": [], "matches": []} | 这里唯一一个把两个通道都明确关闭的扩展 |
Chrome 的规则有 两层。来自 manifest 参考文档:
| 情况 | 谁可以连接 |
|---|---|
| 整个键 不存在 | “所有扩展都可以连接,但没有网页可以连接” |
键存在,ids 未设置或为 [] | “没有扩展或应用可以连接” |
键存在,matches 未设置或为 [] | “没有网页可以连接” |
宽松默认值只对应于整个键缺失的情况。一旦这个键出现,两个子字段都会先处于关闭状态,只有写入值后,通道才会被放开。
Axiom.ai 声明了这个键,但只写了 matches。 因此它的 ids 是未设置的,也就是说没有任何扩展可以给它发消息——扩展通道是关闭的,不是打开的。它打开的是 7 个网页来源,其中只有 1 个是 Axiom 自己的域名:2 个是无法归属的第三方(*://*.tgwc.space/*、*://*.bitmachine.co.uk/*),其余则是 localhost、0.0.0.0、一个 Google APIs 主机,以及一个它并不运营的大型社交平台。
Hexomatic 把扩展通道打开给了 8 个指定 ID。 在键已存在的前提下,基线是 0 个扩展;写入 8 个 ID 就把它扩大到 8 个。它的 6 个 matches 把另一个通道从“没有网页”放宽了,其中两个是 http://localhost:8000/* 和 http://localhost:3000/*,而且还是明文 HTTP——用户机器上任何在这些端口监听的服务,都在允许名单里。
10 个里有 7 个连这个键都没写,包括我们的扩展在内,而这 7 个都处在“扩展之间消息传递的宽松默认值”上。还有两个扩展把这条通道关掉了:Magical 通过 ids: [] 明确关闭,Axiom.ai 则是声明了这个键但从不写 ids。只有 Magical 把两个通道都关了。
最后这一点,正是为什么应该读 manifest,而不是只数权限。一个扩展可以在一个维度上很宽,同时在另一个维度上又是全组里最严格。
没人统计的那个维度:OAuth 范围
manifest 可以包含 oauth2 块,其中的 scopes 代表你在其他公司的账号访问权限——这和前面所有能力都不同,而且任何权限数量都反映不出来。
官方参考:Google OAuth 2.0 scope 列表。
| 扩展 | 请求的 oauth2 scopes |
|---|---|
| Axiom.ai | openid、email、profile、auth/drive、auth/spreadsheets |
| Table Capture | auth/spreadsheets、auth/userinfo.email |
| Agenty | openid、email、profile |
| 其余 7 个,包括我们的 | 未声明 |
https://www.googleapis.com/auth/drive 是更宽的那个。Google 也提供更窄的 drive.file scope,只允许访问应用自己创建的文件,或用户明确挑选的文件;而 auth/drive 则可以查看、编辑、创建和删除用户整个 Drive 里的内容。auth/spreadsheets 对账户能访问到的每个电子表格也是同样的范围。Axiom.ai 和 Table Capture 都把 Google 账号 scope 和 <all_urls> 页面访问配在了一起。
这张表有两个边界。scope 是“请求了”,不是“拿到了”——Google 会展示同意页,用户也可以拒绝,而且扩展可能根本没调用 API。再者,这个 manifest 只能看到通过 Chrome identity 流程完成的 OAuth;如果扩展改成把你带去网页登录页,它在这里就不会写任何东西。那 7 个零的意思是“这个文件里没请求”,不是“没有 Google 账号访问权”——包括我们的扩展也是如此,所以这里才要把这个维度单独报出来,而不是把它当成“足够窄”的证据。
我们自己的扩展,也放在同一把尺子上看
Thunderbit 4.6.4 走的是和其他 9 个完全一样的 manifest 解析器。这个包解包后是 15.9 MiB,共 78 个文件。
已声明的静态行为: host_permissions 和内容脚本匹配里都出现了 <all_urls>。静态脚本在 all_frames 中、document_end 时运行。因为 <all_urls> 包含 file:,用户打开 Chrome 的文件访问开关后,这个 manifest 也能触达本地文件。Thunderbit 还省略了 externally_connectable,所以 Chrome 的默认规则允许任何扩展发消息,但网页不行。
运行时能力上限: permissions 数组里有 9 个字符串:activeTab、commands、debugger、offscreen、scripting、sidePanel、storage、tabGroups 和 tabs。Chrome 只把其中 8 个识别为权限;commands 是顶层 manifest 键,放在这个数组里不会产生效果。Thunderbit 是这组里唯一声明了 debugger 的扩展,它可以把 CDP 连接到某个标签页。manifest 还让 scripting 在运行时可用,方便注册脚本。这些 API 带来的能力已经超出了静态 document_end 那一行,但仅靠 manifest 无法证明 Thunderbit 是否调用了某个特定 CDP 方法,或者是否在更早的时刻注册了脚本。
这个区分也阻止了几种很诱人的、但其实不成立的比较。没有更窄的 cookies 权限,并不能限制 debugger 代码可能访问的内容。没有 clipboardRead,也不能证明就不可能碰到剪贴板。反过来,声明了 debugger 也不等于它真的用了这些路径。要回答这些问题,必须看源码或做运行时追踪,而这次都没有做。
在这个工具里,Thunderbit 在静态站点访问上很宽,在这组里独有 debugger,并且处于 Chrome 默认允许扩展之间消息通信的状态。之所以没有“最强”排名,是因为这次审计没有为 CDP、OAuth、userScripts、剪贴板和 host 访问提供一个共同的威胁模型。
另一种很克制的设计
TexAu 只有 0.3 MiB——是这里最小的,只有其他产品的一半左右——而且它列出了 14 个具体站点,没有用通配符:社交网络、开发者平台、发布平台、聊天产品、商业数据提供商,以及它自己的域名。它的内容脚本也正好匹配这 14 个目标。
看这个 manifest,你能非常准确地知道扩展会在哪些地方生效。这也是一种产品披露——这个目标列表比营销文案更直接地说明了工具是做什么的。
代价也很真实:命名列表无法抓取不在名单上的网站,而且每新增一个目标都得发版。但“14 个命名域名”和“任何存在的 URL”是完全不同的两回事,只有前者是清晰可读的。
两个产品,和名单里写的不一样
Captain Data 的扩展并没有公开分发。 Google 的更新端点对它的扩展 ID 返回的是 HTTP 204 且空 body——这意味着商店不会匿名提供它。相比之下,它的 listing 页面却可以匿名访问:HTTP 200,509,829 字节。但抓取结果里看不到版本号、最近更新时间或安装量;脚本备注也写明,那里出现 null 只能算弱证据,而且没有看到登录墙。这个 ID 是真实的,而且是第一方的,所以最可能的解释是只通过链接分发或未公开上架,但我无法把它和下架区分开,也不会猜测。
Dataflow Kit 根本没有 Chrome 扩展。 它的网站描述的是一个带点选式选择器和 REST API 的托管网页应用。但它还是被放进了 Chrome 扩展候选名单——这类名单通常就是这么来的。
更新情况和覆盖范围,反正都可以查
商店元数据提供了超出权限本身的产品筛选上下文。所有扩展的相同字段都在 2026-07-30 抓取。
| 扩展 | 商店版本 | 最近更新 | 安装量区间 |
|---|---|---|---|
| Thunderbit(我们的) | 4.6.4 | 2026 年 7 月 28 日 | 200,000 |
| Listly | 0.9.6 | 2026 年 7 月 25 日 | 100,000 |
| Axiom.ai | 5.1.0 | 2026 年 7 月 20 日 | 100,000 |
| Table Capture | 11.0.41 | 2026 年 6 月 26 日 | 200,000 |
| Magical | 3.119.1 | 2026 年 4 月 4 日 | 200,000 |
| Agenty | 2.9.7 | 2026 年 2 月 8 日 | 10,000 |
| TexAu | 1.6.6 | 2025 年 8 月 20 日 | 7,000 |
| Clay for Chrome | 1.0.0 | 2025 年 4 月 9 日 | 10,000 |
| Clip to Clay | 1.8.0 | 2025 年 4 月 8 日 | 1,000 |
| Hexomatic | 1.8.4 | 2024 年 9 月 6 日 | 3,000 |
| Captain Data | — | — | 未公开提供 |
有 3 个产品已经超过一年没发版,Hexomatic 则接近两年。对扩展来说,这比对库更重要:Chrome 大约每 4 周就会出一个稳定版,而扩展平台本身也一直在变化。userScripts 的用户门控就在 Chrome 138 改过;如果某个扩展最后一次发版还是 2024 年,那它其实是按一套和现在浏览器不同的规则构建的。
这张表也有两个不是的东西。安装量是 Google 分桶 的——1,000 / 3,000 / 7,000 / 10,000 / 100,000 / 200,000——所以它只能比较数量级,无法比较更细的差别,而且有些产品在自己的营销里会宣传更高数字。最近的日期只说明发版时间,不代表维护质量或权限风险高低。
还有一个很实用的附带结果:这次分析的 10 个包里,商店版本号都和 manifest 版本一致。 我们解析到的 CRX 文件,就是商店今天正在提供的版本,不是陈旧拷贝。
新鲜度会影响你该为哪个产品做多少后续检查,但不会改变 manifest 语义。一个命名域名、而且一年前才更新的扩展,仍然可能比一个昨天才发版的通配符扩展更少接触页面内容。反过来,更新更近的包也可能因为更快跟上 Chrome 变化而成为更好的实际选择。做 shortlist 时,先用商店日期决定要复测谁,再用 manifest 决定谁需要更深入的能力审查。不要把这两列硬合成一个分数。
manifest 不能告诉你的事
已声明的权限是上限,不是行为。 <all_urls> 只表示扩展可能读取你访问的每个页面。它并不意味着它真的这么做,也不意味着任何数据会离开你的机器。要确认实际发生了什么,必须在运行时观察网络流量——那是另一项工作,本次并未执行,前文也都不应被理解为有滥用证据。
相关评估:Chrome 扩展可测试性实验。
还有三个边界要记住:
- Chrome 会对很多事情加门槛。 文件访问默认关闭;
activeTab故意设计得很窄;可选权限需要运行时弹窗;用户在安装时会看到权限列表。 - 广泛权限常常是必要的。 如果工具的任务是“从你当前所在的任意页面提取表格”,那它不可能只靠命名域名白名单工作。范围窄,有时是克制;有时只是产品更小。
- 一版,只看一天。 这里所有数字都来自 2026-07-29 当天商店提供的包。
本次分析里的关键修正
这份公开统计用了 3 条很容易在快速写 manifest 解析器时弄错的规则。第一,<all_urls> 包含 file: 协议,但 Chrome 会把实际文件访问放在用户可控开关后面。第二,必须把 web_accessible_resources.matches 和内容脚本、host 权限一起看;Table Capture 就是在这里明确写了 file://*/*。第三,externally_connectable 的默认值取决于整个键是否缺失,还是键存在但子字段为空。上面的表格是按这些规则一致处理的。
Thunderbit 的权限总数也区分了原始字符串和 Chrome 可识别权限。它的数组里有 9 项,但 commands 属于顶层键,所以这里实际采用的是 8 个可识别权限,加 1 个未识别字符串。最后,静态 content_scripts 声明并不能证明代码调用了 scripting.registerContentScripts 或某个 CDP 方法。这些可能性只算运行时能力,绝不是观察到的行为。原始 manifest、CRX 哈希和解析器输出就是这些归一化步骤的审计轨迹,所以读者不必只靠文字描述来判断。
如果真要决定装哪一个,至少要把 4 个维度分开比较:默认覆盖哪些页面、哪些明确的用户动作会解锁更多范围、会获得哪些浏览器或账号 API、以及哪些外部调用者可以给扩展发消息。Agenty 的 activeTab 模式、Table Capture 受门控的 userScripts、Axiom.ai 的 Drive scope,以及 Thunderbit 的 debugger 声明,根本不在同一条线性尺度上。它们只是对不同威胁问题的不同回答。
下面这张表,把这种比较用在 4 种刻意不同的设计上:
| 扩展 | manifest 里可见的页面范围 | 额外门槛 | 本次审计中的非页面能力 | 外部调用者状态 |
|---|---|---|---|---|
| Agenty | 没有 host 权限或静态内容脚本 | 用户点击后才对当前标签页启用 activeTab | Chrome identity 权限 | 键未声明:任何扩展都可连接,网页不行 |
| Table Capture | 在 <all_urls> 上、所有 frame 中、document_start 时注入静态脚本 | 文件访问默认关闭;userScripts 需要 Chrome 另一个用户门控 | 可选 userScripts、downloads 和 identity | 键未声明:任何扩展都可连接,网页不行 |
| Axiom.ai | HTTP 和 HTTPS 通配访问;在自有域名上于 document_start 注入静态脚本 | 对请求的 Google scopes 进行 OAuth 同意 | 已声明的 Drive 和 Sheets OAuth scopes | 扩展调用者关闭;开放了 7 个网页来源 |
| Thunderbit | <all_urls> host 访问,以及在所有 frame 中、document_end 注入静态脚本 | 文件访问默认关闭;附加 debugger 受 Chrome 控制且用户可见 | 已声明 debugger、scripting、tabs 及相关浏览器 API | 键未声明:任何扩展都可连接,网页不行 |
这张表仍然不会给出赢家。Agenty 的持续页面访问范围很窄,并不能说明它的后端如何处理提交的数据。Axiom.ai 的账号范围也不能和读取当前页面的扩展直接类比。Table Capture 的 userScripts 只有在用户打开单独门控后才生效。Thunderbit 的 debugger 会暴露很宽的浏览器控制面,但 manifest 不会告诉你它调用了哪些 CDP 域或方法。每一行都告诉你下一步该看什么:运行时网络流量、源码、同意流程,还是浏览器 API 追踪。

读 Chrome 安装提示时,这种分离同样重要。单看权限数,根本看不出时机、frame 覆盖范围、OAuth scope、web_accessible_resources,或缺失的 externally_connectable 键。反过来,广泛声明也不能证明采集或外传。manifest 审计真正有用的产出,是一份优先级排序后的运行时测试计划:先找出数据面,再确认用户门槛,然后观察已声明路径是否真的被执行。
如何读你自己的
相关指南:浏览器自动化指南。
- 在
chrome://extensions里点 Details,可以看到已授予的站点访问权限,也可以把不需要持续访问的项目切成 on click。这会收窄上面所说的站点访问部分,但不会影响入站消息(externally_connectable仍然会直接作用于 service worker),也不会影响浏览器级权限。 Thunderbit 的debugger声明在这里尤其重要。Chromium 自己的扩展安全文档写明,debugger API“在某些情况下也可能绕过其他典型限制,例如 host 权限或文件访问”,因此把站点访问设成“on click”并不能限制它能触达什么。这个控制项也不会影响tabs、webNavigation、clipboardRead、downloads,或者optional_permissions里的任何内容。 - 要看原始文件,包位于 Chrome 的
Extensions/<id>/<version>/目录下。需要读 6 个字段:permissions、optional_permissions、host_permissions、content_scripts(看里面的matches、run_at和all_frames)、externally_connectable——记住,键缺失时对扩展调用者是宽松的——以及web_accessible_resources,它的matches可能会写出内容脚本完全没写到的协议。Thunderbit 的最后一个字段里有 2 条<all_urls>记录,把index.html和 2 个打包脚本暴露给每个来源,且use_dynamic_url: false,这意味着页面可以测试这些稳定的扩展 URL 是否可解析。 - 检查 listing 的最近更新时间,与当前 Chrome 版本是否匹配。
披露与资源:本文由 Thunderbit 发布,而我们的扩展也包含在同一张表和同一解析器中。我们的 开源爬虫主文 介绍的是非扩展工具。
简明版
这次读取了 10 款抓取和自动化扩展的 manifest,其中也包括我们的产品。
Agenty 不声明站点访问,也没有内容脚本;它只在你点击它时,对当前标签页生效,体积 2.2 MiB。Axiom.ai 声明了所有 HTTP 和 HTTPS URL,总计 37.1 MiB。Magical 声明了 13 项权限,包括 clipboardRead——而且它也是这里唯一一个把入站消息通道完全关闭的扩展。Table Capture 只声明了 4 项权限,却会在 <all_urls> 上以 document_start 注入到所有 frame——匹配范围和我们的相同,只是更早;它还可按需启用 userScripts。TexAu 体积最小,而且命名了最多目标——14 个模式,分布在 13 个不同属性里。它并不是唯一一个写出目标列表的:Clip to Clay 写了 2 个(clay.com 和一个第三方站点),Hexomatic 和 Clay for Chrome 则写了自有域名。TexAu 是唯一一个命名列表里大多是别人的站点的产品。 10 个里有 5 个能访问你磁盘上的文件,其中 2 个——Axiom.ai 和我们——甚至从未写过 file://。
Thunderbit,也就是我们自己的扩展,处在更宽的一端:<all_urls> 用于 host 访问和静态注入,再加上没有其他扩展声明的 debugger。它处于 Chrome 默认允许任何扩展给它发消息的状态,并且在 Chrome 的文件访问门槛打开后可以访问 file://。
声明只是上限,不是行为,这些内容都不能说明有滥用。但它们都公开可查,而且两端之间的差距,比产品页里看到的任何差距都更大。
试用 Thunderbit 进行网页数据提取 Get Started Free
常见问题
广泛权限是否意味着扩展在做坏事?为什么光数权限不够?
不是。因为只看数量会漏掉两件事。<all_urls> 的意思是扩展可能读取你访问的每个页面;它并不能说明它实际做了什么,也不能说明数据有没有离开你的机器。要从任意页面提取数据的工具,确实不可能只靠命名域名列表工作。这里发现的是差异,不是违规——要验证真实行为,必须做运行时流量分析,而本次没有做。至于数量本身:注入设置不是权限,所以 Table Capture 虽然只写了 4 项权限,却会在 document_start、all_frames、<all_urls> 上注入。而 optional_permissions 根本不会出现在安装提示里——Table Capture 可以在运行时请求 userScripts,从而在页面上下文里运行任意用户脚本;Magical 也可以请求 webRequest。
哪个扩展请求得最少?
Agenty:4 项权限,没有 host 权限,也没有内容脚本。它依赖 activeTab,只在你点击扩展后、并且只在你离开前,才拥有当前标签页的访问权。第二少的是 Hexomatic,只有 2 项权限,而且内容脚本只限于自有域名。
扩展为什么能访问我的本地文件,却不写 file://?
因为 <all_urls> 包含 file: 协议。Listly、Magical 和 Table Capture 都明确写了 file:// 模式——Magical 写在内容脚本里,Table Capture 写在 web_accessible_resources 里。Axiom.ai 和 Thunderbit 则是靠通配符触达的,没有在任何地方写出来。http://*/* 和 https://*/* 都不覆盖 file://。Chrome 会把文件访问放在每个扩展单独的开关后面,默认是关闭的,所以这里的声明是请求,不是授权。
什么是 externally_connectable,为什么不写它反而更宽松?
它用来声明哪些网页来源或扩展 ID 可以给扩展的后台脚本发消息。Chrome 的默认规则是:当这个键不存在时,任何扩展都可以连接,但没有网页可以连接。10 个里有 7 个没写它,包括 Thunderbit。键写出来后,Hexomatic 把扩展通道打开给 8 个指定 ID;Axiom.ai 只打开了 7 个网页来源,同时保持扩展通道关闭;Magical 则把两个列表都写成空,从而把两个通道都关闭了。
Thunderbit 自己的 manifest 声明了什么?
<all_urls> 用于 host 访问和静态内容脚本注入,内容脚本在 document_end、all_frames 中运行;包体 15.9 MiB,共 78 个文件;权限数组里有 9 个字符串。其中 8 个是可识别权限:activeTab、debugger、offscreen、scripting、sidePanel、storage、tabGroups 和 tabs。commands 是顶层 manifest 键,放在这个数组里不会起作用。这里没有别的扩展声明 debugger。Thunderbit 还会在 Chrome 文件访问门槛打开后,通过 <all_urls> 触达 file://,并且省略了 externally_connectable。运行时行为没有执行,所以这份审计并不声称它实际调用了哪些 CDP 或 scripting 方法。


