做了几年接口调试和前端性能优化,我一直对“抓包”这件事又爱又恨。项目前期排查一个线上问题,往往需要在抓包工具和浏览器之间来回切换,配置各种转发规则、信任证书、重启服务,好不容易抓到几个请求,又因为连接断掉得重新来。后来我干脆写了一个开源浏览器插件,核心就两件事:第一,在浏览器里直接完成全栈抓包,不用折腾一堆转发链路;第二,把抓到的请求交给 AI 做自动审计,从海量请求里快速标出可疑点。
这个项目上线后,基本成了我每天开发调试的标配。这里把这套插件从选型到实现、从安装到踩坑的完整过程整理出来,希望能帮到正在做接口调试、性能分析、前端安全排查的同学。
1. 这是做什么的:为什么你需要一个“边抓边审”的浏览器插件
1.1 传统抓包方式的痛点
不少情况下,抓包工具能拿到的信息其实是不完整的。传统桌面抓包工具大多采用中间人转发模式,需要在操作系统、浏览器、甚至手机端分别配置网络出口,还要安装并信任根证书。这套流程用起来最难受的有几个地方:第一,改完后浏览器可能提示证书不受信,个别页面直接访问失败;第二,很多工具默认只处理走转发链路的流量,像 WebSocket 升级、Service Worker 发起的请求、某些后台预取的请求,经常漏得一干二净;第三,在工作电脑上可能没有管理员权限,证书装不上、端口被占满,整个过程彻底卡死。
我还遇到过更头疼的场景:抓包工具刚连上不久,页面里某个长连接就断了。排查了很久才发现,不是工具本身的问题,而是浏览器和服务端都做了连接复用,某些加密握手逻辑在转发链路下校验不通过,于是一段时间后连接被服务端主动断开。这就是为什么很多人会说“抓个包,怎么老掉线”。
而在浏览器插件方案里,这些麻烦基本不存在。插件通过浏览器调试协议直接读取页面网络栈真实产生的事件,不需要改证书,不需要开独立本地服务,也不干预正常连接,因此抓到的数据就是页面实际收发的数据,不用再担心“转发模式下数据和真实场景不一致”的问题。
1.2 AI审计能补上哪一块
抓包本身只是手段,真正难的是从大量请求里发现异常。比如前端调用某个接口时,响应里出现了明文手机号、身份证号;某个内部管理接口在线上环境任何人都能访问;某个静态资源里嵌入了一段看起来像密钥的字符串。这些信息混在成百上千条请求里,人眼扫一遍非常容易漏。
于是我在这款插件里加了一个 AI 审计层。用户选中刚才抓到的请求,点一下“发送到 AI 审计”,插件会把请求的 URL、方法、关键请求头、业务参数以及响应片段汇总起来,调用配置好的大模型接口进行风险识别。AI 能做的事情主要有三类:第一,自动判断当前请求有没有携带敏感标识、令牌是否过期或缺失;第二,从接口返回内容里查找手机号、身份证、密钥等敏感模式,并给出风险等级;第三,结合请求频率、响应大小、Cookie 属性等上下文信息,推断是否存在未鉴权访问、不合理暴露、调试开关遗留等隐患。
说到底,抓包工具解决的是“数据怎么拿到”的问题,AI 审计解决的是“拿到之后该怎么看”的问题。把这两个能力塞进一个浏览器插件里,日常调试效率能高不少。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 插件整体架构:从选型到数据模型
2.1 为什么用 chrome.debugger API 而不是 webRequest
插件要拦截并查看真实响应体,早期最容易想到的是 webRequest API。但实际操作后会发现,在 MV3 版本里 webRequest 默认只能观察到请求的开始和结束,不能直接读取响应体内容;即使配合 declarativeNetRequest 做动态规则,也很难拿到完整的响应数据。
我最终选择了 chrome.debugger API。它底层走的是 Chrome DevTools Protocol,也就是浏览器开发者工具使用的同一套调试协议。使用 chrome.debugger 附加到一个标签页后,可以监听 Network 域里的各种事件,包括请求发送、响应头返回、加载完成,还能主动调用 Network.getResponseBody 获取响应体的完整内容。
这里要注意一个代价:使用 chrome.debugger 时,浏览器顶部会弹出一条“扩展程序正在调试此浏览器”的提示,而且同一个标签页在同一时间只能有一个调试客户端。如果你同时打开了开发者工具,或者另一个抓包扩展正在运行,就可能会冲突。不过对大多数开发调试场景来说,这个提示完全能接受,换来的是精准、完整的数据通道。
2.2 插件内部模块划分
我一开始想着把抓包逻辑直接写在 popup 页面里,省事,后来发现不行。popup 一旦关闭,页面里的状态就跟着销毁了,网络事件还在继续产生,但没人接收,数据全丢。
所以架构上最终分成了四个模块:
- background service worker:负责整个抓包生命周期,注册 chrome.debugger 事件,接收网络事件后写入本地存储;
- 弹窗控制面板:也就是 popup,负责展示“开始抓包/停止抓包”按钮、当前连接状态、抓到的请求列表;
- IndexedDB 数据层:用来持久化请求记录、请求头、响应体以及审计结果;
- AI审计模块:读取选中请求,经过脱敏处理后调用外部模型接口,并把结果写回列表。
把网络事件处理放在 service worker 是关键一步。即使 popup 被关掉了,只要浏览器还在运行,service worker 中的监听函数就能继续接收事件,这样就不会出现“我切出去看了别的页面,回来之后发现漏抓了十几条请求”的情况。
2.3 请求数据是怎么“串”起来的
网络事件不是按一条完整请求为单位一次性给到插件的。Chrome 会把一次网络请求拆成多个事件,比如 Network.requestWillBeSent、Network.responseReceived、Network.loadingFinished,需要我在监听器里先把碎片保存到一个临时 Map 中,等 loadingFinished 到达后再把这条请求合并成一条完整记录。
每一条请求记录我建议至少包含以下字段:
| 字段 | 说明 |
|---|---|
| requestId | 每次网络请求的唯一标识,事件之间靠它关联 |
| url / method | 请求地址和请求方法 |
| status / statusText | 响应状态码与状态描述 |
| requestHeaders | 发送出去的请求头 |
| requestBody | 请求体(如有) |
| responseHeaders | 响应头 |
| responseBody | 响应体内容,过大时按策略截断 |
| mimeType | 响应内容类型 |
| startedDateTime / time | 请求开始时间和总耗时 |
| documentURL | 发起请求的页面地址,用于区分来源页面 |
这个数据结构看起来很简单,但真到实现时会发现很多细节。比如重定向请求,一次页面访问会先后产生多个 requestId,中间用 redirectResponse 串联;比如并发请求特别多时,需要在一个请求完成前先缓存响应头,但如果用户在响应到达前就停止了抓包,这些缓存数据就会成为孤儿数据。我处理的方式是:在停止抓包时做一次清理,超过 30 秒仍未完成的 requestId 直接丢弃。
3. 核心功能拆解:捕获、脱敏、AI审计一条链
3.1 把网络事件串成一条条可读请求
核心代码逻辑其实不复杂,关键是事件顺序要对。我在 service worker 里大致这样写:
javascript复制const entries = new Map();
chrome.debugger.onEvent.addListener((source, method, params) => {
if (!source.tabId) return;
if (method === "Network.requestWillBeSent") {
entries.set(params.requestId, {
id: params.requestId,
url: params.request.url,
method: params.request.method,
requestHeaders: params.request.headers,
postData: params.request.postData || "",
startedDateTime: params.wallTime ? new Date(params.wallTime * 1000).toISOString() : new Date().toISOString(),
documentURL: params.documentURL,
status: 0,
responseHeaders: {},
responseBody: "",
time: 0
});
}
if (method === "Network.responseReceived") {
const entry = entries.get(params.requestId);
if (entry) {
entry.status = params.response.status;
entry.statusText = params.response.statusText;
entry.responseHeaders = params.response.headers;
entry.mimeType = params.response.mimeType;
}
}
if (method === "Network.loadingFinished") {
const entry = entries.get(params.requestId);
if (!entry) return;
chrome.debugger.sendCommand(
{ tabId: source.tabId },
"Network.getResponseBody",
{ requestId: params.requestId },
(result) => {
if (result && result.body) {
entry.responseBody = result.body;
entry.base64Encoded = result.base64Encoded || false;
}
entry.time = params.timestamp ? params.timestamp * 1000 : 0;
saveEntryToIndexedDB(entry);
entries.delete(params.requestId);
}
);
}
});
这里有两个值得注意的细节。第一,getResponseBody 是异步回调,在 loadingFinished 事件里调用它时,要小心响应体过大导致回调延迟,所以我在外层做了一层超时控制,超过 3 秒没有返回就放弃读取。第二,请求体并不是每次都能拿到,如果是 multipart/form-data 且文件较大,postData 可能为空,这时应该只记录文件流的分界信息,避免把二进制内容塞进数据库。
响应体如果是图片、字体等二进制资源,getResponseBody 会返回一个 base64 编码的字符串,默认情况下我不会存储这类内容,因为尺寸很大且审计价值较低,只在交互面板里提供“点击查看前 2KB 预览”的能力。
3.2 发送给 AI 前,先做脱敏和裁剪
一开始我做 AI 审计时特别激进,直接把完整请求头和响应体都拼接成文本发给模型服务。第一个版本上线后我自己测试了一下,发现 Cookie、Authorization、临时令牌全部明文出现在请求里,如果这部分数据落到第三方接口,问题就大了。
所以在正式版里,所有数据要经过脱敏器处理之后才允许发送。默认脱敏规则包括:
- Cookie、Set-Cookie、Authorization、Proxy-Authorization 等敏感请求头统一替换为
[FILTERED]; - URL 中常见的 token、session、key、sign 参数值替换为
[HIDDEN]; - 请求体、响应体中匹配到手机号、身份证号、AK/SK 样式的字符串,替换为 masked 形态;
- 单个响应体只保留前 4000 个字符,超出的部分截断并标记。
这里最重要的是“默认不发送”。我在 UI 里专门加了一个预览面板,用户点击“AI 审计”之后,会先看到这次要发出去的数据长什么样,确认没有遗留的敏感内容,再点“确认发送”。这个交互虽然多了一步,但能避免很多隐私风险,尤其是在公司内部代码环境里,审计数据一旦外发,谁都没法承担责任。
3.3 内置审计规则与结果展示
AI 判断不能全凭模型自由发挥,那样结果太飘。我给每条请求先做一个本地预检,把明显问题打上标签,再把标签连同上下文一块提交给模型,让模型在已有结论基础上补充分析。实际跑下来准确率明显更高,模型也不容易出现“把一个普通查询参数当成密钥”的误判。
本地预检规则大致包括:
| 预检规则 | 触发条件 | 风险等级 |
|---|---|---|
| 明文凭证 | URL 或请求体中出现 password、token、apiKey 参数 | 高 |
| 敏感数据返回 | 响应体匹配手机号、身份证号、银行卡号模式 | 高 |
| 未鉴权访问 | 该接口返回数据但请求头无 Cookie、Authorization | 中 |
| CORS 配置过宽 | 响应头 Access-Control-Allow-Origin 为 * | 中 |
| 调试开关遗漏 | 响应体包含 mock、debug、test data 等标记 | 低 |
模型返回的结果会以风险列表的形式展示,每条结果都包含风险类型、严重程度、出现位置和修复建议。用户可以直接按风险等级排序,优先处理高危问题,不用再从几百条请求里逐个翻找。
4. 上手实操:从零跑通一次抓包与AI审计
4.1 加载插件并授权
插件基于 Chromium 的扩展机制开发,Chrome、Edge 都能跑。拿到项目源码后先安装依赖构建一次,然后在浏览器地址栏输入 chrome://extensions/,打开右上角的“开发者模式”,点击“加载已解压的扩展程序”,选择项目根目录下的构建产物目录即可。
加载完成后,扩展卡片上会出现插件图标。第一次点击图标时,插件会先请求“读取浏览历史”相关权限吗?不会。它的核心权限只有两个:一是 debugger 权限,用于附加到标签页调试;二是 storage 权限,用来保存用户配置和审计记录。如果你选择把数据存储在 IndexedDB 中,那么连 storage 都可以省掉一部分,只在存 API Key 时用到。
首次点击图标时,插件会弹出一个授权确认窗口,询问是否允许调试当前标签页。这里注意别直接点拒绝,否则后续抓包功能无法启动。授权是一次性的,之后同一标签页再次点击就能直接开始监听。
4.2 开始一次真实抓包
开始抓包前,建议先把目标页面刷新一下,让插件从页面加载的第一步开始记录。因为 Network.enable 只能监听到开启之后发生的事件,如果先打开一个已经加载很久的页面再开启,很多静态资源和首屏接口是拿不到的。
实际操作流程:
- 打开你想要调试的目标页面;
- 点击插件图标,确认授权后点击“开始捕获”;
- 刷新目标页面,或者主动触发页面里的操作,比如点击按钮、提交表单、滚动加载列表;
- 操作完成后切回插件面板,点击“停止捕获”;
- 此时能看到所有记录的请求列表,按请求 URL、状态码、耗时、MIME 类型等信息排序。
我在面板顶部提供了一个“当前页面”筛选器,会自动把来自其他标签页或者后台 Service Worker 的请求过滤掉。这样在做单页面调试时,请求列表不会混入无关流量,找起来更省心。
第一次使用时,很多人会问“为什么我打开插件点开始捕获,页面再刷新,有些请求还是没抓到”。十有八九是因为目标页面在另一个窗口,插件 attach 的是当前激活的标签页。插件设计上允许用户在下拉菜单里选择一个标签页进行调试,切换过去再刷新,就不会漏了。
4.3 配置模型并执行AI审计
抓完包之后,选中列表里的一条或者多条请求记录,点击“AI 审计”,第一次使用时先去“设置”页配置模型接口。
在设置页里需要填写接口地址、API Key、模型名称三项。接口地址需要兼容 OpenAI 格式,因为很多开源模型或者国内服务商都提供类似接口,这样用户就不必依赖某一个固定服务商。填好后点击“测试连接”,插件会发一个轻量请求验证配置是否可用。
配置完成后,回到请求列表,勾选想要审计的请求,点击“AI 审计”。插件会先弹出预览弹窗,展示脱敏后的数据包内容。这一步非常关键,我会仔细看一遍 Authorization、Cookie、响应体里有没有漏网的敏感信息。确认无误后点击“发送”,等待模型返回结果。
审计结果会在请求记录下方展开,以多个风险条目的形式显示。每条风险都会附带一个“查看原始位置”的超链接,点击后可以定位到具体是哪个响应字段触发了提醒。这个定位功能在做修复时非常有用,不用自己再去数据里找一遍。
4.4 自定义 Prompt 与批量审计
插件默认的审计 Prompt 是“你是一个 Web 安全专家,请分析以下 HTTP 请求和响应,找出敏感数据泄漏、鉴权缺失、错误配置等问题”。做本地开发还好,放到生产环境或者公司内部系统上,就需要按团队规范定制。
设置页支持自定义 Prompt 模板,还可以设置系统提示词,在发送请求时会把用户写的内容拼到消息体的 system message 里。比如有些团队只关心接口是否返回了过多冗余字段,那我就会把 Prompt 改成“重点检查响应体每个字段是否为前端渲染所必需,给出冗余字段列表”。这个改动不涉及代码逻辑,只是提示词层面调整,插件本身不需要做额外适配。
批量审计则是把多条请求合并到一次模型调用中发送,减少 API 调用次数。插件默认合并最多 20 条请求,每条请求先做一轮裁剪,只保留 method、URL、状态码、关键请求头、响应体前 500 字符。合并之后的数据量依然不小,所以建议勾选时别贪多,尽量选可疑请求而不是全部请求。
5. 我在开发过程中踩过的坑
5.1 debugger 连接“掉线”的真相
我最早把 chrome.debugger 的 attach 逻辑写在 popup 里,结果发现当我把 popup 缩小、切换到别的窗口时,抓包经常中断。表面上看是“插件掉线了”,其实是因为 popup 被浏览器回收,事件监听函数随之销毁。
后来我改成在 background service worker 里管理 debugger 连接,popup 只负责发指令和展示数据,问题就解决了。需要注意一点:MV3 的 service worker 不是一直存活的,它可能在空闲 30 秒后被浏览器杀掉。为了保活,我采用了一个比较取巧的办法:在抓包开始时通过 chrome.alarms 每 20 秒触发一次心跳事件,心跳里只是简单记录时间戳,不做别的逻辑。这样能有效避免 service worker 被回收。
如果抓包过程中发现事件丢失,可以先看扩展详情页里的 service worker 状态。如果它显示休眠且没有自动唤醒,优先检查心跳定时器是否注册成功,这是最常见的“掉线”原因。
5.2 大响应体把内存撑爆
第一次拿到真实数据时,我抓到一个接口返回了 80MB 的 JSON 数据。当时插件直接卡死,因为把整个响应体保存进了 IndexedDB,还在列表页做了全文检索索引。之后我加了一个响应体大小判断逻辑:超过 2MB 的响应体不读取,只记录 headers 和状态码;超过 200KB 的响应体默认只保存前 200KB,并在记录里标注“已截断”。
同时,内存里的 entries Map 也需要做淘汰策略。现在默认最多保存 500 条未完成请求,超过后按最早请求先清理。这样即使页面上有上千个接口,插件也不会因为堆积未完成的网络事件而崩溃。
5.3 自定义 API 域名被浏览器 CORS 拦下
AI 模型的接口地址是用户配置的,不可能提前写进扩展的 host_permissions 列表。在 MV3 里,扩展页面跨域请求同样受 CORS 约束,如果不配置对应域名,fetch 会直接失败。
我最后的处理方案是把“模型接口域名”做成一个可动态授权的列表。用户在设置页填入接口地址时,如果域名不在当前权限范围内,插件会调用 chrome.permissions.request 弹窗申请对该域名的访问权限。授权一次后,后续请求就顺畅了。如果用户拒绝授权,插件不会强制请求,而是退回到“仅本地预检”模式,至少还能给出一部分风险提示。
很多插件之所以在用户配置自定义接口时报错,就是因为漏了这步动态授权,只想着在 manifest 里写死几个已知域名。这个坑踩到一次就记住了。
5.4 权限与隐私边界
这个插件本质上能看到用户在某个页面产生的全部流量,因此权限边界一定要克制。我要求自己遵守三条底线:
- 默认不主动上传任何数据。只有用户手动点击“AI 审计”,才可能把脱敏后的请求内容外发;
- 不采集用户浏览历史,不把审计记录回传到插件作者服务器;
- API Key 只保存在本机扩展存储中,并在 UI 里明确提示不要填入高权限的生产环境密钥。
开发过程中我还遇到过一个细节问题:响应体里有 HTML 时,如果直接原样展示,扩展页面会执行其中内嵌的脚本,虽然浏览器有隔离机制,但为了避免不必要的风险,插件在展示数据时统一把 <script> 标签转义为纯文本。
这个项目目前还在持续迭代,我自己的使用习惯已经从“出了问题再开抓包工具”变成了“日常开发就一直开着插件”。毕竟它不会干扰正常网络连接,随时看随时抓,而 AI 审计又帮我把从数据到结论的时间压缩了一大截。如果你经常需要排查接口问题,或者对 Web 应用的敏感数据暴露情况心里没底,可以试着把开发环境里的浏览器抓包改成这种插件式工作流,用一段时间后应该能明显感觉到差别。
