
上个月,我做了一件早该做的事:打开浏览器扩展管理页面,逐一查看已安装插件的权限声明——结果差点被咖啡呛到。
一个标签页管理插件,拥有访问我所有浏览数据的权限;一个号称"简单截图"的工具,能读取并修改我访问过的每一个页面;还有一个三年前随手安装、早就忘得一干二净的 CSS 取色器,居然有权限读取剪贴板,并向一些我从未听说过的域名发送网络请求。
如果你是一名开发者,而且最近没有审计过自己浏览器里的扩展程序,那你实际上就是在运行一段来历不明的代码,而且它对你日常工作的访问权限高得吓人。闲话少说,直接讲怎么解决这个问题。
问题本质很简单:浏览器扩展运行在浏览器的高权限环境中,而你每天的工作——访问代码仓库(不管是 Gitee 还是阿里云Code)、管理云服务器、登录生产环境控制台、处理用户数据——全都在这个环境里进行。
一个恶意或被入侵的扩展可以:
虽然浏览器应用市场会对扩展进行审核,但这个流程并非万无一失。扩展会被出售给新主人,然后推送恶意更新;依赖库可能被入侵;还有些扩展只是悄悄收集超出实际需要的数据,因为这就是它们的商业模式。
先看看你到底装了多少扩展,每个扩展又拿到了哪些权限。
打开浏览器扩展管理页面(Chrome 系输入 chrome://extensions,Firefox 输入 about:addons),开启开发者模式。然后逐个点击"详情",审查每个扩展申请了哪些权限。以下是需要警惕的红灯信号:
// 扩展权限中的红标信号:
"读取和更改您在所有网站上的所有数据" // 核弹级权限——极少数扩展真正需要这个
"管理您的下载内容" // 可以在你电脑上写入文件
"读取您的浏览记录" // 数据收集的金矿
"与协同工作的原生应用程序通信" // 可以与本地进程通话
然后做一次快速清理。卸掉那些你根本不用的扩展。我从 14 个精简到 5 个——说真的,早该这么干了。
对于保留下来的扩展,确认它们是否开源。如果开源,你可以直接读源码再决定是否信任;如果是闭源且申请了大量权限,这是你应该主动去做判断的,不能稀里糊涂地默认接受。
每个浏览器扩展都有一个 manifest.json 文件,里面声明了它的所有权限。评估任何扩展源码时,这是第一个要看的东西。
{
"manifest_version": 3,
"name": "My Extension",
"permissions": [
"activeTab",
"storage"
],
"host_permissions": [
"https://specific-api.example.com/*"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
]
}
读法如下:
permissions:API 级别访问。activeTab 相对安全(只在用户点击时激活当前标签页);而 tabs、webRequest、cookies 就重多了。host_permissions:扩展可以交互的网站范围。<all_urls> 意味着通行所有网址。一个权限范围得当的扩展只申请它实际需要的域名。content_scripts 配置了 matches: ["<all_urls>"]:这个扩展会向你的每一个访问页面注入 JavaScript。它可以读取 DOM、拦截表单提交、修改页面内容。最小权限原则在这里完全适用。一个正常的扩展只申请它需要的权限。截图工具不应该需要 cookies 权限,深色模式切换也不应该需要 webRequest。
让我意外的一点是:写一个基础的浏览器扩展真的很简单。如果你需要某个简单功能却要依赖一个来路不明的扩展,考虑直接自己做一个。
下面是一个最小化的扩展,实现一个真正有用的功能——加一个按钮,把当前页面的标题和 URL 复制为 Markdown 链接:
// background.js
chrome.action.onClicked.addListener(async (tab) => {
// 格式化为 Markdown 链接
const markdown = `[${tab.title}](${tab.url})`;
// 使用 offscreen API 复制到剪贴板
//(Manifest V3 从 service workers 中移除了直接访问剪贴板的能力)
await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: (text) => {
navigator.clipboard.writeText(text);
},
args: [markdown]
});
});
对应的 manifest 只需要这些:
{
"manifest_version": 3,
"name": "Copy as Markdown Link",
"version": "1.0",
"permissions": ["activeTab", "scripting"],
"action": {
"default_title": "Copy as Markdown Link"
},
"background": {
"service_worker": "background.js"
}
}
加载方式:打开 chrome://extensions → 点击"加载解压的扩展程序"→ 选择你的文件夹。就这样,不需要构建步骤,不需要打包工具,也不需要提交到应用商店。
这里的 activeTab 权限是关键——它只在用户明确点击扩展图标时,才授予对当前标签页的访问权,而且仅在点击那一刻有效。对比一下:一个做同样事情的扩展,如果申请了 <all_urls>,那权限差距就天壤之别了。
如果想做得更彻底,可以写脚本自动化审计流程。Chrome 会把扩展数据存在一个固定位置:
# macOS
ls ~/Library/Application\ Support/Google/Chrome/Default/Extensions/
# Linux
ls ~/.config/google-chrome/Default/Extensions/
# 逐一检查每个扩展的 manifest
for dir in ~/Library/Application\ Support/Google/Chrome/Default/Extensions/*/; do
latest=$(ls -t "$dir" | head -1)
manifest="$dir$latest/manifest.json"
if [ -f "$manifest" ]; then
name=$(python3 -c "import json; print(json.load(open('$manifest')).get('name','unknown'))")
perms=$(python3 -c "import json; print(json.load(open('$manifest')).get('permissions',[]))")
hosts=$(python3 -c "import json; print(json.load(open('$manifest')).get('host_permissions',[]))")
echo "Extension: $name"
echo " Permissions: $perms"
echo " Host access: $hosts"
echo ""
fi
done
运行之后,你就能快速拿到所有扩展及其申请权限的汇总。导出到文件,找个时间慢慢审查——你可能会惊讶于发现的东西。
完成这轮审计之后,我给自己立了几条规矩:
浏览器扩展生态存在的信任问题,其实和我们在 npm 包、VS Code 扩展,以及所有插件市场看到的如出一辙。攻击面巨大,审核流程有漏洞,大多数用户从不深究。
但作为开发者,我们有一个独特的优势——我们能读代码。我们看得懂 manifest.json,能为自己需要的简单功能编写替代方案,遇到维护良好且开源的扩展时,应该优先选择它们,而不是闭源方案。
不是说所有事情都要从零开始。但你至少应该知道自己浏览器里运行着哪些代码,它们对你的核心工作流有着怎样的访问权限。今天抽出半小时审计一下你的扩展,未来你会感谢现在的自己。