文章指出这些 AI 应用普遍基于 Electron 构建,共享相似的网络栈,因此探测结果可迁移至其他同类应用。 作者借此揭示了 Copilot 的运行时行为,并分享了配置代理的具体步骤。 🔗 阅读原文 via AIHOT · https://aihot.virxact.com/items/cmsp0g9v20242rort4zh7mzx0
在过去几年里,出现了大量由人工智能驱动的应用程序和AI功能。像Slack这样的现有企业迅速在其功能列表中新增了AI功能:https://slack.com/intl/en-gb/help/articles/25076892548883-Guide-to-AI-features-in-Slack。对于像Cursor、Notion、ChatGPT Desktop和Claude Desktop这样的AI原生应用,人工智能一直是其存在的基本原因。
这些应用发布的AI功能越多,我就越倾向于去研究它们的内部运作。希望我能揭开一些运行在背后的机制;至少,我能在桌面应用开发方面学到一两件事。
巧合的是,我注意到我每个月用尽Copilot的额度的速度越来越快。这最终促使我选择了一个主要的实验对象。我决定深入研究VS Code和Copilot。
上述所有应用的共同点是它们都是用Electron构建的:https://www.electronjs.org/。Electron是一个JavaScript框架,它帮助开发者构建和发布桌面应用程序。通俗地说,它的工作原理是将Node.js运行时与HTML、CSS和JavaScript资源打包,然后通过Chromium渲染:https://www.chromium.org/Home/。
这样就不需要为不同平台编写多套本地语言的代码(例如,Windows使用C#,macOS使用Swift),使开发者能够从单一代码库轻松构建跨多个平台运行的桌面应用程序。(本地模块和某些打包步骤仍然通常需要针对不同平台进行处理,但大部分应用逻辑是共享的。)
由于它们共享Electron,因此它们共享一个大致的架构,这意味着我在研究一个应用时学到的东西应该可以迁移到其他应用。
我的第一反应是直接浏览VS Code的源码:https://github.com/microsoft/vscode,看是否能回答我的问题。问题是,我还没有一整套问题——而在数百万行代码中寻找问题将花费我太多的时间或太多的tokens。
源代码告诉你一个应用可以做什么;发现它在运行时实际做了什么则更具挑战性。尤其是在你还不知道自己在寻找什么的时候。
还有第二个问题。VS Code 是我开始使用的应用程序中一个例外:它的源代码(或者至少大部分)是开放的。而 Claude、ChatGPT、Codex、Notion 和 Slack 并非如此。
这促使我走向逆向工程的路线:先被动地观察流量,让请求和响应告诉我哪些问题值得去问,然后再去查看源代码以确认(或否定)我所看到的内容。
这意味着要亲自接触 Electron 的架构和网络栈——这些技能以后拥有也不会有坏处。
到现在我们知道,Electron 应用附带 Chromium。浏览器为应用的基于网页的 UI 提供渲染引擎,但它也提供了渲染进程可以用来进行 HTTP 和 WebSocket 连接的网络栈。
这是启用应用与远程后端通信的常见(且推荐的)方式,但它不是唯一的方式。应用也可以使用 Node 的 http/https/fetch 进行 HTTP 请求。当你试图拦截时,请求所走的路径就变得很重要。
在某些情况下,如 VS Code,应用会有一个解耦的架构,其中有一组独立的进程充当扩展主机。这有助于在不同职责之间保持清晰的界限;在 VS Code 中,就是在 UI、代码 IDE 功能和插件/扩展之间保持明确界限。
拦截应用网络流量的经典方法之一是搭建一个代理服务器,并配置应用使用它。
代理充当中间人(MITM):它拦截客户端的 HTTP 请求,将其转发到服务器,并将服务器响应传回客户端。
趣闻:在企业网络环境中,为了流量检查目的,类似的方法非常常见,尤其在高度监管的行业中。相应地,主要的开源工具之一叫做 mitmproxy:https://www.mitmproxy.org/,我们将在接下来的步骤中使用它。
一个重要的细节是,如今大多数网络流量都通过安全 HTTP(HTTPS)进行。这意味着流量是使用 TLS 加密的。
通过信任 mitmproxy 本地生成的证书颁发机构(CA),客户端可以接受 mitmproxy 为每个目标动态生成的证书。你不会得到一个端到端加密连接,而是得到两个连接:一个在应用程序和 mitmproxy 之间,另一个在 mitmproxy 和目标服务器之间。
因此,mitmproxy 可以解密请求、检查请求、在上游建立独立的 TLS 连接,并将响应转发回应用程序。
如果你不想跟着代码操作,只想看到结果,可以跳过本节内容。
在 macOS 上,最简单的方法是使用 brew:
我们需要在 VS Code 中更改一些设置,以通过 mitmproxy 路由其流量。你可以使用快捷键组合 Cmd+Shift+P 并搜索“用户设置”来更改这些设置。然后,你需要确保以下设置具有如下值:
HTTP 代理 : http://localhost:8080(mitmproxy 将在此端口监听连接)
Http Proxy Strict SSL : 未勾选(我们希望跳过对 mitmproxy 证书与 CA 列表的验证)
Http: Proxy Support : 覆盖(强制扩展使用代理支持)
在进行这些更改后,请务必重启 VS Code。
开始操作前的最后一步是启动 mitmproxy 的 Web UI:
等待几秒钟,你应该会看到来自 VS Code 的一些网络流量开始通过它流动。
你会注意到顶部有一些文本字段。现在大部分可以忽略;最有用的是第一个字段 Search。该字段提供强大的搜索功能,如关键字搜索、正则表达式等。例如,如果我们特别关注 VS Code 向其扩展市场 API 发出的请求,可以简单地使用 marketplace 作为过滤字符串。
这将匹配对 https://marketplace.visualstudio.com 及其所有子路径的所有请求。
即使做了所有这些操作,你的代理仍可能无法捕获扩展流量。如果 VS Code 的扩展主机进程组变得过时,就会发生这种情况。要确认此问题,请在终端运行:
确认显示的日期。如果它与您重启 VS Code 时的日期和时间不同,则扩展宿主进程很可能已过期。解决方法很简单:
在 VSCode 中,打开命令面板(Cmd+Shift+P)
运行“开发人员:重新启动扩展主机”
然后重新运行你的 ps grep——你现在应该能看到 Code Helper 的新 PID,其时间戳为今天
快速浏览 mitmweb 中 VS Code 的网络请求,你会注意到大多数请求都与 GitHub 或 GitHub Copilot 相关。在我们在 VS Code 或 Copilot 扩展中按下任意键之前,一些 HTTP 请求已经发出。
在引导阶段,VS Code 和 Copilot 发出的请求可以分为以下几类:认证与会话、配置与策略、MCP 注册表、仓库与会话上下文、模型发现以及最近的仓库。
在接下来的段落中,我们讨论我发现的每种类型请求的情况:请求和响应的头信息及有效载荷内容。
这是 Copilot 启动时做的第一件事。它获取一个 OAuth:https://en.wikipedia.org/wiki/OAuth 令牌,将其交换为短期有效令牌,并验证用户的权限。这个流程是一个相当常规的 OAuth 流程;如下图所示。
在发出任何 LLM 请求之前,Copilot 会检查你的账户/计划可以使用哪些模型和代理功能。
有两种不同类型的请求。首先,会向 /models 发送请求。这个初始请求返回 Copilot 中可用模型的通用列表。
然后,第二个请求会发送到 /agents/swe/models。这是一个特定请求,用于查询与软件工程(SWE)相关的代理功能可用的模型。
引导程序之后,事情变得有趣了。
在本实验的所有 Copilot 测试中,我选择了自动模式。在我发送每条消息后,我能够在任何模型回答之前捕获到一个发向 /models/session/intent 端点的请求。
这里发生的情况是:你的提示会根据可能的意图进行评分,比如代码生成、调试、推理和工具使用。意图分类结果帮助Copilot确定哪些模型能满足任务。
这其实并不是什么秘密;此类行为在Copilot的文档中有描述:https://docs.github.com/en/copilot/concepts/models/auto-model-selection。不过,看到背后的实际请求和回应还是很有趣。
我开始尝试内联补全和幽灵文本:https://docs.github.com/en/copilot/concepts/completions/code-suggestions,观察通过HTTP发送的内容。我已经知道内联补全会把当前文件注入提示作为上下文;事情本来就是这样运作的。所以到目前为止并不意外。
但我还是想知道还有什么其他东西被寄来了,于是做了一个小测试。我把一个假秘密放进了一个.env文件——那个臭名昭著的文件,我们所有孩子都被告诫不要泄密,但有些人仍然会犯规。
编辑这个文件没有触发任何HTTP请求,我觉得这很好。然后我打开了一个完全无关的pyproject.toml,开始输入。
结果,在我做的时候,收到了以下完成请求:
我第一反应是:好吧,我干脆禁用 Copilot 来处理 .env 文件。结果发现它已经被禁用了;我都忘了这件事。
这无关紧要;请求通过 pyproject.toml 文件中的按键触发,该文件中内联补全功能被正常启用。
心里记下:关闭 .env 本身或其他“秘密”扩展的内联补全都不会改变任何东西,因为请求本身并没有被它触发。但其他请求也可能被触发。
我在系统提示中看到过session_store_sql工具定义,针对我截获的许多完成请求。以下是从其中一个请求中获得的工具描述:
不过,之后我没有看到任何工具调用结果被反馈回去。工具可能根本没被调用。为了确认,我上去尝试通过在聊天中问一个简单的问题来强制调用工具:“我这周做了什么?”
接下来发生的是模型与一个名为 session-store.db 的本地 SQLite 数据库之间的来回交互,而我之前不知道它的存在:
通过查看这些对话,我了解到,session_store_sql 是 Copilot 的 Chronicle 工具的一部分:https://github.blog/changelog/2026-06-02-gain-insights-across-your-agent-sessions-with-chronicle/,它允许对 session-store.db 运行 SQL 查询。这个数据库存储了会话摘要、你工作的代码仓库和分支。
它还存储了你所有的提示以及对应的 LLM 响应。Copilot 保留了一个可查询的历史记录,记录你向它提出的所有问题,并在需要时从中获取信息。
一个引人注意的地方是模型事先并不知道数据库的结构。它最初尝试了下面的查询,但失败了。
There has been a flurry of AI-powered apps and AI features in the last couple of years. Incumbent players like Slack have swiftly added AI features to its roster:https://slack.com/intl/en-gb/help/articles/25076892548883-Guide-to-AI-features-in-Slack . For AI-native ones like Cursor, Notion, ChatGPT Desktop and Claude Desktop, AI was always part of the raison d’être.
The more AI features these apps released, the more I became inclined to look at their inner workings. Hopefully I would be able to uncover a bit of what’s running under the hood; at the very least, I would learn one thing or two about desktop app development.
Coincidentally, I noticed I started exhausting my Copilot credits earlier and earlier each month. This ended up pulling me towards selecting a main candidate for my experiments. I decided to dive deep into VS Code and Copilot.
Common amongst all of the apps above is the fact that they are built using Electron:https://www.electronjs.org/ . Electron is a JavaScript framework which helps developers build and distribute desktop applications. In layman’s terms, it works by bundling a Node.js runtime along with HTML, CSS and JavaScript artifacts, which are then rendered via Chromium:https://www.chromium.org/Home/ .
This removes the need of having multiple codebases in native languages for different platforms (for instance, C# for Windows and Swift for macOS), making it easier for developers to build desktop applications that run across multiple platforms from a single codebase. (Native modules and certain packaging steps still often require per-platform handling, but the bulk of the application logic is shared.)
Because they share Electron, they share a rough architecture, which means whatever I learned probing one should transfer to the others.
My first instinct was to just skim through VS Code source:https://github.com/microsoft/vscode and see if it could answer my questions. The problem was that I didn’t have a full set of questions yet - and hunting for them across millions of lines of code would cost me either too much time or too many tokens.
Source code tells you what an app can do; discovering what it actually does at runtime is more challenging. Especially when you still don’t know what you’re looking for.
There was a second problem. VS Code is an exception amongst the apps I had started with: its source code (or at least the majority of it) is open. This is not the case for Claude, ChatGPT, Codex, Notion, and Slack .
That started pushing me toward the reverse engineering route : passively watch the traffic first, let the requests and responses tell me which questions would be worth asking, and only then go to the source to confirm (or disprove) what I was seeing.
It meant getting my hands dirty with Electron’s architecture and network stack - skills that wouldn’t hurt to have afterwards.
By now we know that Electron apps ship with Chromium. The browser provides the rendering engine for the application’s web based UI, but it also provides a network stack that renderer processes can use for HTTP and WebSocket connections.
This is a common (and recommended) option for enabling apps to speak to a remote backend, but it is not the only one. Applications can also make HTTP requests using Node’s http/https/fetch . Which path the request takes becomes important when you’re trying to intercept it.
In some cases, like with VS Code, the application will have a decoupled architecture, where there’s a separate group of processes that acts as an extension host . This helps maintain clear boundaries between distinct responsibilities; in the case of VS Code, a clear boundary between UI, code IDE functionality and plugins/extensions.
One of the classic ways to intercept an application’s network traffic is by standing up a proxy server, and configuring this application to use it.
The proxy acts as a man-in-the-middle (MITM) : it intercepts HTTP requests from a client, forwards them to a server, and relays back the server responses to the client.
Fun fact: a similar approach is quite common in corporate network environments for traffic inspection purposes, especially in highly regulated industries. Fittingly, one of the main open source tools used for this is called mitmproxy:https://www.mitmproxy.org/ , which we will use in the next steps.
An important detail is that most of the network traffic nowadays happens via secure HTTP (HTTPS). This means traffic is encrypted using TLS.
By trusting mitmproxy’s locally generated certificate authority (CA), the client can accept the certificates mitmproxy generates on the fly for each destination. Instead of a single end-to-end encrypted connection, you get two: one between the application and mitmproxy, and another between mitmproxy and the destination server.
mitmproxy can therefore decrypt the request, inspect it, establish a separate TLS connection upstream, and forward the response back to the application.
If you don’t want to follow along with the code and would just like to see the results, feel free to skip this section.
On macOS, the simplest way is to use brew:
We need to change some settings in VS Code to route its traffic via mitmproxy. You can change these settings by using the hotkey combination Cmd+Shift+P and searching for User Settings. You will then need to make sure that the settings below have the following values:
Http Proxy : http://localhost:8080 (mitmproxy will be listening for connections at this port)
Http Proxy Strict SSL : unchecked (we want to skip verification of mitmproxy’s certificate against a list of CAs)
Http: Proxy Support : override (force proxy support for extensions)
After making these changes, be sure to restart VS Code.
The final step before getting started is starting up mitmproxy’s web UI:
Give it a few seconds and you should start seeing some network traffic from VS Code flowing through it.
You will notice some text fields on top. You can ignore most of them for now; the most useful is the first one, Search . This field provides powerful search capabilities like keyword search, regex, etc. For instance, if we are particularly interested in the requests made by VS Code to its Extensions Marketplace API, we can simply use marketplace as a filter string.
This will match all requests to https://marketplace.visualstudio.com and all its subpaths.
It could be that even after all this dance, your proxy still doesn’t capture extension traffic. This can happen if VS Code’s Extension Host process group becomes stale. To confirm this, run from the terminal:
Confirm the date that is displayed. If it’s not the same date and time from when you restarted VS Code, the extension host process is most likely stale. Solving this is simple:
In VSCode, open the Command Palette ( Cmd+Shift+P )
Run “Developer: Restart Extension Host”
Re-run your ps grep afterward - you should now see new PIDs with today’s timestamp for Code Helper
Quickly skim through the network requests from VS Code in mitmweb and you will notice that the majority of them are related to either GitHub or Github Copilot. Before we hit a single key in VS Code or in the Copilot extension, some HTTP requests are made.
Requests made by VS Code and Copilot during the bootstrap stage can be allocated into one of the following categories: Auth & Session, Config & Policy, MCP Registry, Repo & Session Context, Model Discovery and Recent repos.
In the next paragraphs, we discuss what I found out about each of these types of requests: what’s included in headers and payloads for requests and responses.
This is the first thing done by Copilot at startup. It fetches an OAuth:https://en.wikipedia.org/wiki/OAuth token, exchanges it for a short-lived token, and validates the user’s entitlements. The flow is quite a regular OAuth one; it is described in the diagram below.
Before making any LLM requests, Copilot checks which models and agent capabilities are available for your account/plan.
There are two separate kinds of requests. First, a request is made to /models . This initial request returns a general list of models which are available within Copilot.
Then, a second request is made to /agents/swe/models . This is a specific request to find out which models are available for agentic capabilities related to Software Engineering (SWE).
Post bootstrap is where things get interesting.
I selected Auto mode for all the Copilot tests in this experiment. After I sent each message, I was able to capture a request to a /models/session/intent endpoint before any model answered.
What’s happening here is: your prompt gets scored against possible intents, such as code-gen , debugging , reasoning and tool-use . The intent classification outcome helps Copilot define which of the available models will fulfill the task.
This is not really a secret; such behaviour is described in Copilot’s documentation:https://docs.github.com/en/copilot/concepts/models/auto-model-selection . Still, it was fun to see the actual requests and responses behind it.
I started to play around with inline completions and ghost text:https://docs.github.com/en/copilot/concepts/completions/code-suggestions , watching what was being sent via HTTP. I already knew inline completions inject the current file into prompts as context; that’s how it’s supposed to work. So no surprise here thus far.
But I still wondered about what else got sent, so I did a small test. I dropped a fake secret into a .env file - the infamous file all of us kids are told not to commit, but some of us still do.
Editing this file didn’t trigger any HTTP requests, which was good, I thought. I then opened a completely unrelated pyproject.toml, and started typing in it.
Lo and behold, the following completion request went out while I was doing it:
My first thought was: fine, I’ll just disable Copilot for .env files. Turns out it was already disabled; I had forgotten about it.
It wouldn’t have mattered; the request was fired from keystrokes in the pyproject.toml file, where inline completions were happily enabled.
Mental note : turning inline completions off for .env itself or any other “ secret ” extension changes nothing, because the request is not being triggered by it. But other requests can be triggered.
I had seen a session_store_sql tool definition in the system prompts for many of the completion requests that I intercepted. Here is the tool description obtained from one such request:
However, I didn’t see any tool call results being sent back after that. The tool was probably not being called. To double check, I went on and tried to force a tool call by asking a simple question in the chat: “What did I work on this week?” .
What followed was a back-and-forth between the model and a local SQLite database called session-store.db , which I didn’t know existed:
As I learned by looking into these conversations, session_store_sql is part of Copilot’s Chronicle tool :https://github.blog/changelog/2026-06-02-gain-insights-across-your-agent-sessions-with-chronicle/ , which lets it run SQL queries against session-store.db . This database stores session summaries, repos and branches you have worked on.
It also stores all of your prompts, along with their corresponding LLM responses. Copilot is keeping a queryable history of everything you’ve asked it, and reaching into that history when it’s needed.
One thing that stood out was that the model didn’t know the schema ahead of time. It initially tried the query below, which failed .