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 的 ProcmonProcess 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。这个数字让我皱眉——我的测试项目总共才几百 KB4.2GB 是什么概念要么它在传别的东西要么这个流量统计包含了重传和协议开销要么它在传压缩后的某种索引。我做了个对比把测试项目换成一个空目录重新启动 ZCode同样的静置操作。这次上行流量降到了 200MB 左右持续时间缩短到 8 分钟。这个对比很说明问题——流量的规模与项目内容强相关空目录传得少有内容的目录传得多。这基本排除了“纯心跳包”或“纯遥测”的可能。注意对象存储域名本身不代表恶意。很多工具用对象存储做会话快照、日志归档、模型缓存。关键是传的内容和触发条件不要看到 aliyuncs 就下结论。3.2 第二轮抓包锁定进程与触发条件第一轮只能说明“有流量”不能说明“是 ZCode 的流量”。第二轮我用资源监视器把流量归因到进程。操作方法是在资源监视器的“网络”标签页里按“发送字节数”排序观察哪个进程的发送量在爬升。结果很明确ZCodeHelper.exe桌面端的后台辅助进程是主要发送方ZCode.exe主进程发送量很小。这符合很多 Electron 类应用的架构——主进程管 UI辅助进程管后台任务。这也解释了为什么很多人只盯着主进程看觉得“它没传东西”。接下来是触发条件测试。我设计了几个场景分别观察流量场景操作上行流量持续时间A打开项目不操作4.2GB38分钟B打开项目只编辑一个文件4.5GB41分钟C打开项目执行一次对话式重构5.1GB45分钟D不打开项目只启动软件200MB8分钟E打开项目关闭“项目索引”选项210MB9分钟这张表是整次排查的核心证据。场景 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 后续会不会改我不知道。但我知道的是只要你还在这类工具里跑真实项目就值得花半小时做一次我上面说的排查。半小时换一个安心这笔账怎么算都划算。