那天夜里,抓包窗口里滚过一串明晃晃的纯文本,里面躺着我们项目的整个.env文件。我做的不过是把GitHub Copilot的流量引向一个叫mitmproxy的开源代理,就像往一座密闭的水管接口处塞了一面透视镜。结果流出来的东西比我想象的脏得多。这篇文章不是安全通告,也不打算教你如何起诉谁。它是一次纯粹的技术凝视——把一款由全球最大代码平台背书、日活可能超过任何老牌IDE插件的AI编码工具,放到网络层下细细端详。而整个过程的起点,不过是出于一个朴素到近乎偏执的疑问:它到底背着我的编辑器,朝云端发了什么?
透明代理:撕掉Copilot那层加密外衣
一个躺在代码与网络之间的监听人
mitmproxy不是什么新兵器。十年前它就扮演着那个让人又爱又恨的中间人角色,只是这次它的目标不再是浏览器明文密码,而是藏在Electron壳里的AI助手。因为所有基于Electron构建的应用——VS Code、Atom、Discord、Slack,甚至Obsidian——都共享一套由Chromium内核浇筑的网络栈,你用来拦截Web应用的经验,对桌面端几乎原样移植。我在macOS上启动mitmproxy,生成一份自签的CA根证书,然后告诉VS Code:你遇到所有TLS握手,先过我这道闸。
这个动作本身平凡无奇,却像一个无伤大雅的开关,把Copilot从“近乎本地化响应”的幻象中扯了出来。一旦证书被操作系统信任,Electron应用会不加反抗地接受这个代理,因为你亲手把自己的机器训练成了一块驯顺的信任孤岛。记住这个细节:当你在公司电脑上点击“安装根证书”时,整个Electron生态的网络通信就在你眼前摊开了。对Copilot而言,没有额外的SSL Pinning,没有第二层自定义加密。它相信宿主,宿主相信代理。于是所有流量,裸露得如同刚从水里捞起。
证书导入之后,流量开始说实话
我花了大概二十分钟完成证书配置和环境变量的指定——把HTTP_PROXY和HTTPS_PROXY指向本地8080端口,重启VS Code,然后开始正常编码。令人背脊发凉的并不是数据量。Copilot发出的请求体结构极其规矩,JSON格式整洁得像教科书,每个字段都有清晰的名称:prompt、context、suffix、languageId。但当你把字段名和值对照着看,就陡然意识到了危机:context字段里塞满了当前打开文件的全部内容,包括注释、代码块、以及——是的——那些被.gitignore小心呵护的文件。只要你打开过含有数据库密码、API Key、内部域名映射的配置文件,那些内容就已经离开你的磁盘,以一种与Git推送完全不同的路径,静默滑向远端的推理节点。
更要命的是,这些请求没有携带任何标记来区分“敏感”与“非敏感”。对Copilot的提示工程而言,.env文件和app.js文件仅仅是两段不同后缀的文本,而文本本身的语义权重,由模型在服务端再次解码。也就是说,在你的终端里被星号掩盖的密钥,在离开你硬盘的瞬间,恢复成了一张只有SkyNet才能欣赏的明信片。这里的威胁不是来自“恶意攻击者”,而在于你根本不知道自己在交出什么。
悄悄记下的历史,比API请求更隐蔽
如果单纯是请求体携带敏感内容,尚可归咎于“不假思索的全量上下文拼接”。真正让我头皮发麻的是响应之后的残留。mitmproxy能保存完整的会话日志,我在某个深夜翻看那些.flw文件时,发现Copilot返回的补全建议、你接受并插入的代码片段、甚至是你采纳后立刻撤销的那次尝试,都以明文形式堆积在一个本地数据库里。VS Code扩展使用标准IDE历史机制存储这些片段,但没有任何加密措施。设想一个场景:你从公司笔记本上退了GitHub账号,清空了编辑器缓存,甚至手快地删除了工作区.state目录。可Copilot留下的那堆历史记录文件,依然像有人把你的代码草稿本翻开来摊在桌上。
这不是漏洞,这是设计决定的副产品。Copilot必须拥有记忆才能提供有状态的体验——它需要知道你刚才改动了哪一行,才能判断下一个光标位该补什么。于是“历史”成了产品功能不可剥离的一部分。但问题在于,当一个工具的记忆如此完整而又不透明时,它的信任模型就坍塌成了一块单点故障。你无法审计这些记忆是否会在某次云端同步、某次崩溃报告中被捎带出去。你只能选择信,或者不信。
有状态的AI,无遮拦的风险敞口
上下文不是护身符,是它最脆弱的韧带
AI编码工具最喜欢标榜的词就是“上下文”。上下文感知、上下文理解、全库上下文。没错,这是它们区别于传统自动补全的根本能力。但如果你把上下文从功能清单里单独拎出来,就会发现它恰恰是整个系统安全模型中承压最大的那根韧带。为了产生精准的补全,Copilot不仅读取当前文件,还会采撷相邻文件、同级目录、甚至你刚刚切走的标签页。这意味着它在一次平淡无奇的Tab键背后,织起了一张比你预想大得多的信息网。
网一旦撒开,收网的不是你。请求离开客户端后,经过微软或GitHub的网关,进入一个你可能永远没资格盘问的推理集群。尽管官方承诺不会将代码片段用于其他用户的模型训练,但“用于改善服务”和“实时推理”之间的界限在隐私政策里经常模糊成一句你能念却解不开的符咒。更讽刺的是,这些文件里不乏你靠高级SSH密钥保护的私密仓库内容。本地最强的安全措施,在Copilot把文本攥成JSON对象的那一刻,就被轻松绕过。你给仓库上了装甲,但AI助手开了扇侧门。
明文历史的困境:从辅助变成档案
我们再谈深一点。任何一个开发者在领用一款AI助手时,心里默认的契约是:你帮我更快写码,我不要了你会忘掉。可实际上,Copilot在本地的行为更像一位勤勉的档案员。我们刚才提到那些明文存储的会话片段,它们可以被文本编辑器轻易打开、拷贝、解析。设想你的电脑遭到恶意软件感染,或者你误将工作目录打包传上公开网盘,这些历史文件里就可能埋葬着比源码更危险的东西——你曾经在一段废弃的尝试中硬编码过的临时密码、为调试而暴露的内部URL、甚至贴在注释里的个人凭证。它们早已被你遗忘,却被Copilot的“记忆”忠实封存。
这里没有任何阴谋,纯粹是工程上对于“状态”的功利对待。设计师希望下次打开编辑器时,你能立刻恢复上次的会话上下文,让AI像一个没关机的同事一样继续帮你。为了达到这种流畅体验,他们选择把状态本地化存储,而未投入同量级的精力去分级加密。这个选择与绝大多数生产力工具的路线一致——Slack也会在本地保留聊天缓存——但代码上下文有别于聊天日志,它天然携带更高密度的秘密。把代码上下文当普通文本对待,就是对秘密的误判。
Electron生态的通病,不是Copilot一个人的错
同样的网络栈,复制了同样的信任盲区
我把这层分析迁移到其他AI工具上时,感受几乎像是把同一个探针插进不同牌子的电源插座。Cursor、Warp、Tabby、甚至某些本地模型的桌面客户端,只要基于Electron且未额外实现证书锁定,全都会乖乖吞下你的系统代理设置。如果你能拦截Copilot,那么拦截它们也不过是改一行配置的事。这意味着整个AI编码工具赛道,在底层网络审计层面共用着一块透明的天花板。
这件事的吊诡之处在于:开发者社区里流传着各种防御范式的讨论——零信任、最小权限、沙盒——但当你真正把工具丢进透明代理照妖镜时,发现的却是集体性的朴素。没有哪家厂商在文档中明确告诉你,我们传了哪些数据,以何种格式,走过哪几跳路由。它们把“为了提供智能”当作一把万能钥匙,打开了所有通向远程推理的大门。而作为交换,你交出的不只是代码,是你在那个时刻的全部工作记忆。
我们要的不是更少的智能,而是知情的交换
没人说要回到没有AI编码的时代,那种呼吁既天真又反动。但在把更多上下文拱手送出之前,每一个开发者至少应该拥有两样东西:一份可读的流量白皮书,和一个随时可开启的本地审计通道。这次用mitmproxy做的实验,其实只是把理应内置于产品中的透明度,用野蛮手段夺了回来。可笑的是,这种原始抓包竟成了目前最有效的审查方式,因为产品自身没有提供任何同等的观察窗口。
如果我们继续默许“云端推理必然带走数据”这套叙事,那很快所有IDE都将演变为完美的信息摄取器。届时你在本地加密、在CI/CD中掩码、在合规审查中战战兢兢保护的一切,都会被一只帮倒忙的AI手悄悄复印几十份,分散存储于各处。这不是科幻,《Copilot Behind mitm》这个实验已经演示了前半程。后半程怎么写,选择权不在微软手里,在每一个看清流量真相之后仍然按下快捷键的编码者手里。

