1. 事件背景与排查动机
1.1 一个让我后背发凉的发现
事情起因很简单。上周三晚上,我在做一个内部工具的重构项目,本地代码涉及一些尚未公开的业务逻辑。因为赶进度,我同时开着 ZCode 的桌面端做代码补全,顺手用它的 CLI 跑了几次批量重构任务。半夜收工前,我习惯性地打开路由器的流量监控面板扫一眼——这个习惯是早年做安全审计留下的,结果看到一条持续了将近四十分钟的上行流量,目标地址指向一个我不认识的域名,峰值速率稳定在 2MB/s 左右。
我第一反应是系统更新或者某个同步盘在跑,但排查了一圈:系统更新已关闭,网盘客户端没启动,浏览器标签页也没有大文件上传。最后把范围缩小到当时唯一活跃的、带网络权限的进程——ZCode 的后台服务进程。这个发现让我瞬间清醒,因为那个项目目录里有一份还没提交到任何远程仓库的算法草稿,如果它真的被传出去了,后果不是“尴尬”两个字能概括的。
这篇文章就是那次排查的完整记录。我会把整个思路、用到的工具、每一步的判断依据、以及最后得出的结论都摊开讲。不管你是刚接触 AI 编程工具的新手,还是已经重度依赖这类工具的老手,这篇东西都值得你花时间看完——因为代码安全这件事,出事之前所有人都觉得“跟我没关系”。
1.2 为什么“AI 编程工具上传代码”这件事值得警惕
先把话说清楚:AI 编程工具需要联网,这是它的工作原理决定的。代码补全、对话式改代码、跨文件重构,这些能力背后都是把上下文发给远端模型推理,再把结果传回来。所以“上传”本身不是问题,问题是上传了什么、传到哪里、传了多久、有没有经过你的同意。
我见过太多人用这类工具时的默认心态是“大厂产品,应该没问题”。但现实是,一个 AI 编程工具在后台可能同时做几件事:把当前文件发给模型做补全、把整个项目索引发给服务端做语义检索、把 Git 历史打包做上下文增强、把 checkpoints 快照存到对象存储做会话恢复。这四件事里,只有第一件是你明确感知到的,后面三件往往藏在隐私政策的某一行小字里。
更麻烦的是,很多工具的“项目索引”和“会话快照”功能默认开启,而且不会在界面上给你一个显眼的提示。你以为它只看了你光标所在的那个函数,实际上它可能已经把你整个仓库的目录结构、最近几次 commit 的 diff、甚至 .env 文件里的配置都读了一遍。这不是危言耸听,是我在这次排查中亲眼在日志里看到的东西。
所以这次排查的核心目标很明确:搞清楚 ZCode 到底在后台传了什么、传给了谁、触发条件是什么、能不能关掉。下面我按排查的实际顺序来讲,你可以跟着我的思路一步步复现。
2. 排查前的环境准备与工具选型
2.1 我用的监控工具组合及选择理由
排查这类问题,工具选型很关键。我最终用的是三件套:Wireshark 抓包 + 系统级进程网络监控 + 文件系统审计。为什么是这三个而不是别的,我解释一下背后的逻辑。
Wireshark 负责看“传了什么”。它的优势是能解密 TLS 之外的元数据,虽然现在绝大多数流量都是 HTTPS,你看不到明文内容,但你能看到目标 IP、域名(通过 SNI)、传输的数据量、持续时长、传输的时间模式。这些元信息足够判断“是不是在上传代码”以及“上传的规模有多大”。
系统级进程网络监控负责看“谁在传”。我用的是 Windows 上的资源监视器配合netstat轮询,Linux 上对应的是ss加lsof。这一步的目的是把流量精确归因到某个进程,避免误判。因为一台机器上同时跑着浏览器、同步盘、各种后台服务,不锁定进程的话,你看到的流量可能根本不是 ZCode 的。
文件系统审计负责看“读了什么”。这一步最容易被忽略,但恰恰是最关键的。因为一个进程要上传你的代码,它必须先读取你的代码文件。我用的是 Windows 的 Procmon(Process Monitor)和 Linux 的inotifywait,监控 ZCode 进程对项目目录下所有文件的读取操作。如果它读了 .git 目录、读了 .env、读了整个 src 目录,那基本就实锤了。
提示:抓包工具只能看到加密后的流量,不要指望能看到代码明文。排查的重点是“行为模式”而不是“内容还原”。如果你需要确认具体传了什么,得从进程的文件读取行为反推。
2.2 排查前必须做的三件事
在正式开始之前,有三件事必须先做,否则你的排查结果会不可信。
第一,固定测试环境。我专门建了一个干净的测试项目目录,里面放几个特征明显的文件:一个叫SECRET_CANARY_001.txt的文本文件,内容是一串唯一字符串;一个正常的 Python 项目结构,带 .git 目录和几次 commit;一个 .env 文件放着假的 API key。这样做的目的是,如果这些文件出现在网络流量或远端存储里,我能立刻确认是 ZCode 干的,而不是别的进程。
第二,关闭所有无关网络活动。浏览器、同步盘、系统更新、其他 IDE 的遥测,全部关掉。这一步是为了降低噪音。我第一次排查时就是因为浏览器后台在跑一个视频预加载,导致流量曲线被污染,白折腾了半小时。
第三,记录基线。在启动 ZCode 之前,先抓五分钟的包,记录下正常的背景流量。这样后面看到异常流量时,你能对比出增量。基线数据是判断“异常”的锚点,没有基线,你看到任何流量都会觉得可疑。
2.3 测试项目的构造细节
测试项目的构造有讲究,不能随便建个空目录。我当时的目录结构是这样的:
zcode-test/ ├── .git/ # 带 3 次 commit 的真实 Git 历史 ├── .env # 假 API key: FAKE_KEY_CANARY_XYZ ├── SECRET_CANARY_001.txt # 唯一字符串: CANARY-7f3a9b2c ├── src/ │ ├── main.py # 正常业务代码 │ ├── utils.py │ └── config.py # 引用了 .env ├── tests/ │ └── test_main.py └── README.md这个结构覆盖了几个关键场景:Git 历史(测试它是否打包 .git)、敏感配置(测试它是否读 .env)、普通源码(测试它是否索引整个 src)、以及一个明显的金丝雀文件(用于最终确认)。金丝雀文件的内容我特意设计成全局唯一的字符串,这样即使流量被加密,我也能通过文件读取行为确认它被访问过。
3. 排查过程实录:从流量异常到行为定性
3.1 第一轮抓包:发现可疑的上行流量
启动 ZCode 桌面端,打开测试项目,然后什么都不做,就让它静置。这是关键——我要看它在“用户无操作”的情况下会不会主动传东西。很多人排查时一边操作一边看流量,结果分不清是用户触发的还是后台自动的。
静置大约两分半后,Wireshark 里出现了第一条可疑记录。目标域名是一个*.aliyuncs.com的子域,SNI 显示为对象存储服务。上行流量开始爬升,持续了大约 38 分钟,累计上行约 4.2GB。这个数字让我皱眉——我的测试项目总共才几百 KB,4.2GB 是什么概念?要么它在传别的东西,要么这个流量统计包含了重传和协议开销,要么它在传压缩后的某种索引。
我做了个对比:把测试项目换成一个空目录,重新启动 ZCode,同样的静置操作。这次上行流量降到了 200MB 左右,持续时间缩短到 8 分钟。这个对比很说明问题——流量的规模与项目内容强相关,空目录传得少,有内容的目录传得多。这基本排除了“纯心跳包”或“纯遥测”的可能。
注意:对象存储域名本身不代表恶意。很多工具用对象存储做会话快照、日志归档、模型缓存。关键是传的内容和触发条件,不要看到 aliyuncs 就下结论。
3.2 第二轮抓包:锁定进程与触发条件
第一轮只能说明“有流量”,不能说明“是 ZCode 的流量”。第二轮我用资源监视器把流量归因到进程。操作方法是:在资源监视器的“网络”标签页里,按“发送字节数”排序,观察哪个进程的发送量在爬升。
结果很明确:ZCodeHelper.exe(桌面端的后台辅助进程)是主要发送方,ZCode.exe主进程发送量很小。这符合很多 Electron 类应用的架构——主进程管 UI,辅助进程管后台任务。这也解释了为什么很多人只盯着主进程看,觉得“它没传东西”。
接下来是触发条件测试。我设计了几个场景,分别观察流量:
| 场景 | 操作 | 上行流量 | 持续时间 |
|---|---|---|---|
| A | 打开项目,不操作 | 4.2GB | 38分钟 |
| B | 打开项目,只编辑一个文件 | 4.5GB | 41分钟 |
| C | 打开项目,执行一次对话式重构 | 5.1GB | 45分钟 |
| D | 不打开项目,只启动软件 | 200MB | 8分钟 |
| E | 打开项目,关闭“项目索引”选项 | 210MB | 9分钟 |
这张表是整次排查的核心证据。场景 A 和 D 的对比说明:只要打开项目,就会触发大规模上传,跟用户是否操作无关。场景 E 说明:关闭项目索引选项后,流量回落到基线水平,这直接指向了“项目索引”功能就是上传的源头。
3.3 第三轮:文件系统审计确认读取范围
流量和进程都锁定了,但还差最后一环:它到底读了我的哪些文件?这一步用 Procmon 做。过滤条件是“进程名包含 ZCode”且“操作是 ReadFile”,然后看它访问的路径。
结果让我有点意外。它读取的范围包括:
- 整个
src/目录下的所有.py文件 .git/目录下的HEAD、config、refs/、objects/下的部分文件.env文件SECRET_CANARY_001.txt(这个最要命,它连非代码文件都读了)README.md
.git目录被读取这件事特别值得说。Git 的objects目录里存的是所有历史版本的压缩对象,读它意味着工具能重建你的完整提交历史,包括那些你以为“已经删掉”的敏感信息。很多人在 commit 里不小心提交过密钥,后来用新 commit 删掉了,但历史还在。如果工具打包了 .git,那些历史密钥就一起被传走了。
.env被读取也在意料之外。通常 AI 补全只需要看代码文件,读 .env 说明它的索引策略是“把项目当做一个整体来理解”,而不是“只理解当前编辑的文件”。这种策略对补全质量有帮助,但对安全是灾难。
3.4 关键证据链的整理
把三轮的结果串起来,证据链是这样的:
- 打开项目后,ZCodeHelper 进程向对象存储发起持续上行传输
- 传输规模与项目内容量正相关,空项目传得少
- 关闭“项目索引”选项后,传输回落到基线
- 文件系统审计显示,索引过程读取了源码、Git 历史、.env 和金丝雀文件
- 金丝雀文件的唯一字符串在读取行为中出现,确认它被纳入了索引范围
这五条合在一起,结论是:ZCode 的“项目索引”功能会在后台读取项目内的多类文件(包括 Git 历史和配置文件),打包后上传到对象存储,用于服务端的语义检索和会话恢复。这个过程默认开启,用户无感知,且上传规模远超“当前文件”的范围。
4. 上传内容的深度分析与风险评估
4.1 checkpoints 机制到底存了什么
排查到后面,我专门去翻了 ZCode 的本地缓存目录,想看看 checkpoints 到底存了什么。在 Windows 上,路径大致在%APPDATA%/ZCode/下面,有几个子目录值得注意:checkpoints/、index/、cache/。
checkpoints/目录里是一系列按时间戳命名的快照文件。我打开其中一个看,发现它并不是简单的“当前文件副本”,而是一个包含了文件路径、文件内容哈希、以及部分文件内容的 JSON 结构。更关键的是,它记录了文件的历史版本——也就是说,你在会话中改过的每一版代码,它都留了档。
index/目录里是项目索引的本地缓存,包含文件路径列表、符号表、以及一些向量化的片段。这个目录的大小和项目规模正相关,我的测试项目索引缓存大约 15MB,一个真实的中型项目轻松上到几百 MB。
cache/目录里是模型交互的缓存,包括请求和响应的摘要。这部分相对没那么敏感,但也能看出你最近在做什么类型的任务。
提示:checkpoints 的存在本身是为了“会话恢复”——你关掉软件再打开,能接着上次的上下文继续。这个功能对用户体验有帮助,但代价是你的代码历史被完整留档,而且这个档可能不止存在本地。
4.2 Git 历史被读取意味着什么
这是我觉得最需要单独拎出来讲的一点。很多人对代码安全的理解停留在“当前文件别泄露”,但 Git 历史是另一个维度的风险。
Git 的objects目录用内容寻址存储,每个文件版本、每次提交、每个目录树都是一个对象。一旦工具读取了这个目录,它就能重建出你项目的完整演化过程。这意味着:
- 你三个月前误提交的数据库密码,即使后来删了,历史里还在
- 你曾经写过的、后来重构掉的内部算法,历史里还在
- 你的分支结构、提交信息、作者信息,全都在
我实测过,用git log --all --full-history能翻出来的东西,比大多数人想象的多得多。而 ZCode 的索引过程读取了.git/objects下的文件,这就意味着这些历史信息有可能被纳入了上传范围。
4.3 风险评估:哪些项目最危险
不是所有项目都面临同等风险。根据这次排查的结果,我整理了一个风险分级:
| 项目类型 | 风险等级 | 原因 |
|---|---|---|
| 含未公开算法/业务逻辑的私有项目 | 高 | 核心资产可能被索引上传 |
| 含 .env / 密钥配置的项目 | 高 | 配置文件被读取 |
| 有敏感 Git 历史的项目 | 高 | 历史提交可能含密钥 |
| 开源项目 / 学习项目 | 低 | 代码本来就公开 |
| 纯前端 UI 项目 | 中 | 业务逻辑价值相对低,但配置仍敏感 |
这个分级不是绝对的,但能帮你快速判断自己该不该紧张。如果你在做的是公司内部系统、商业产品、或者任何“代码本身就是资产”的项目,那这次排查的结论对你直接适用。
5. 应对方案与配置调整实操
5.1 立即能做的三件事
排查完当天我就做了三件事,按优先级排序:
第一,关闭项目索引。在 ZCode 的设置里找到“项目索引”或“代码库索引”相关的选项,关掉。关掉之后补全质量会下降,因为它只能看到当前文件,看不到跨文件的上下文。但对我来说,安全优先于补全质量。实测关闭后,上行流量回落到基线水平。
第二,把敏感项目移出索引范围。ZCode 支持配置“排除目录”,我把.git、.env、以及存放密钥的目录都加了进去。这个配置在设置里的“索引排除”或“文件排除”里,支持 glob 模式,比如**/.env、**/.git/**。
第三,用独立的测试账号跑敏感项目。如果某个项目实在需要 AI 辅助,我会用一个专门的、不关联任何真实凭证的账号来跑,并且项目里不放真实密钥,全部用占位符。
5.2 索引排除配置的具体写法
排除配置的写法因工具而异,但逻辑是相通的。我以 ZCode 的配置为例,说明一下思路。它的排除配置接受 glob 模式,我当时的配置大致是这样的:
{ "indexing": { "exclude": [ "**/.git/**", "**/.env", "**/.env.*", "**/secrets/**", "**/*.pem", "**/*.key", "**/node_modules/**", "**/dist/**", "**/build/**" ] } }这个配置的核心是:把版本控制目录、环境配置、密钥文件、以及构建产物全部排除。构建产物排除是为了减少索引体积,顺带降低上传量。密钥类文件的排除是硬性要求,不能省。
注意:排除配置只影响“索引”范围,不影响“当前文件补全”。也就是说,即使你排除了 .env,当你在编辑器里打开 .env 时,它的内容仍可能被发给模型做补全。所以最稳妥的做法是:敏感文件根本不要在 ZCode 里打开。
5.3 网络层的兜底方案
配置层面的调整依赖工具“守规矩”。如果你想要更强的保证,可以在网络层做限制。我的做法是在路由器或本机防火墙上,对 ZCode 的进程做出口流量限制,只允许它访问必要的 API 域名,禁止访问对象存储域名。
这个方案的好处是不依赖工具的配置是否生效,坏处是可能影响正常功能(比如会话恢复会失效)。我自己的取舍是:在跑敏感项目时开启网络限制,跑普通项目时关闭。这样兼顾了安全和体验。
具体操作上,Windows 可以用防火墙的出站规则,按进程路径做限制;Linux 可以用iptables或nftables配合cgroup做进程级限制。这里不展开命令细节,因为不同系统差异大,核心思路是“按进程 + 按目标域名”做白名单。
6. 常见问题与排查技巧速查
6.1 排查过程中踩过的坑
坑一:把浏览器流量误判为工具流量。第一次排查时我没关浏览器,结果一个后台视频预加载贡献了 1.5GB 上行,差点让我得出错误结论。教训是:排查前必须清场,把所有无关网络活动关掉。
坑二:只看主进程,漏掉辅助进程。ZCode 的上传是ZCodeHelper.exe干的,主进程ZCode.exe的流量很小。如果你只盯着任务管理器里那个显眼的进程,会得出“它没传东西”的错误结论。教训是:按流量排序,看所有相关进程。
坑三:以为 HTTPS 就看不到东西。虽然看不到明文,但 SNI、目标 IP、数据量、时间模式这些元信息足够定性。不要因为“加密了”就放弃排查,行为模式本身就是证据。
坑四:忽略文件系统审计。流量只能告诉你“传了”,文件审计才能告诉你“传了什么范围”。两者结合才能形成完整证据链。
6.2 快速自查清单
如果你不想做完整排查,只想快速判断自己的风险,可以按这个清单过一遍:
| 检查项 | 操作方法 | 风险信号 |
|---|---|---|
| 项目索引是否开启 | 看设置里的索引选项 | 开启且有敏感项目 |
| 是否读取 .git | 用 Procmon 过滤进程读操作 | 读取了 .git/objects |
| 是否读取 .env | 同上 | 读取了 .env |
| 上行流量规模 | 资源监视器按进程排序 | 与项目规模正相关的大流量 |
| 目标域名 | Wireshark 看 SNI | 对象存储类域名 |
这个清单不能替代完整排查,但能帮你在五分钟内判断“要不要紧张”。
6.3 长期使用的安全习惯
排查完之后,我调整了自己的使用习惯,这里分享几条:
- 敏感项目用独立的、隔离的环境跑 AI 工具,不混用真实凭证
- 定期检查工具的隐私设置,尤其是版本更新后,默认值可能被重置
- 用金丝雀文件做定期自检,放一个唯一字符串的文件在项目里,偶尔看看它有没有被异常读取
- 不要把“大厂产品”等同于“安全”,安全是配置出来的,不是品牌给的
7. 我对这类工具的整体看法
说实话,这次排查没有让我得出“ZCode 是恶意软件”的结论。它的索引和 checkpoints 机制,从产品设计角度讲是合理的——为了补全质量和会话恢复,它需要理解你的项目全貌。问题不在于“它做了这件事”,而在于“它没有把这件事清楚地告诉用户,也没有给用户一个显眼的开关”。
我用过不少 AI 编程工具,几乎每一家都在做类似的事,区别只是透明度和默认值。有的工具会在首次打开项目时弹窗问“是否允许索引此项目”,有的则默默开启。ZCode 属于后者。这不是它一家的问题,是整个行业的通病。
所以我的态度是:不妖魔化,但也不放松。工具是工具,安全是自己的事。你不可能指望每个工具都把你的代码当命根子,但你可以通过配置和习惯,把风险控制在自己能接受的范围内。
最后分享一个我自己的小技巧:我会在项目根目录放一个.zcodeignore文件(如果工具支持的话),把敏感路径写进去,同时配合网络层的白名单。双保险下来,即使工具的配置在某次更新后失效,网络层还能兜住。这个组合我用了几个月,实测下来比较稳。
至于 ZCode 后续会不会改,我不知道。但我知道的是,只要你还在这类工具里跑真实项目,就值得花半小时做一次我上面说的排查。半小时换一个安心,这笔账怎么算都划算。