ZCode后台自动上传加密代码包,用户无法解密且开关无效

智谱AI编码工具ZCode被曝在后台自动扫描工作区,将包含完整Git历史及敏感配置的代码打包加密并上传至阿里云OSS。逆向分析显示其采用信封加密,私钥仅存于服务器,导致本地密文无法由用户或客户端解密。尽管隐私政策提及数据收集,但界面设置无法真正关闭该上传管道,引发对代码资产安全与隐私合规的质疑。

一次常规的磁盘清理,牵出了 ZCode 后台数据管道的问题。ZCode 是智谱推出的官方 AI 编码桌面应用;有技术用户在其本地数据目录中发现,只要保持登录,客户端会在后台扫描整个工作区,把完整的 .git 历史、LFS 资产缓存、reflogs 以及全局应用配置一并打包、加密,并直接上传到 Aliyun OSS。更令人在意的是,加密所用 RSA 公钥由服务器在请求时下发,本地磁盘上留下的数百 MB 密文既无法由用户解密,也无法由 ZCode 客户端自身解密。

线索起点:pending 目录中的 313MB 加密包

~/.zcode 是 ZCode 的数据根目录。用户清理磁盘时,各部分体积大致为:会话数据库与执行日志约 257MB;Computer Use 相关目录约 130MB,包含捆绑应用与运行时依赖;v2/checkpoints/ 约 303MB,是主要怀疑对象。

私钥仅在服务器,本地密文无法解密

在 v2/checkpoints/ 中,发现一个 313MB 的 .enc 文件,以及对应的状态元数据:

{
  "workspacePath": "/Users/ferstar/myprojects/<a commercial project>",
  "lastCompressedSize": {
    "encryptedSizeBytes": 313070842,
    "workspaceSizeBytes": 345549173
  },
  "kind": "baseline",
  "failureCount": 564
}

元数据指向一个商业项目路径。结合日志可还原出基本过程:客户端扫描该项目,排除了 node_modules 和少量其他目录;上传失败被记录 564 次,加密包一直留在本地 pending/ 目录等待重试。该仓库总量约 10GB,而被打包的 345MB 几乎全部是核心代码资产。

从日志到逆向:上传被重建为两段式流程

进一步解开客户端的 app.asar 后,上传链路被还原为如下流程:

sequenceDiagram
    participant C as ZCode client
    participant S as zcode.z.ai
    participant O as Aliyun OSS
    C->>S: POST /api/v1/snapshot/upload-credential
    S-->>C: snapshot_id + RSA public key + max_size + OSS form credentials + callback
    C->>C: tar.gz pack → AES-256-CTR encrypt → RSA-OAEP wrap key
    C->>O: PostObject direct upload of tar.gz.enc
    O->>S: callback confirms receipt

整个管道分为两个阶段:

  1. 向协调方请求凭证:客户端请求编码为 VITE_ZCODE_ENDPOINT_ORIGIN 的 https://zcode.z.ai,服务器返回 OSS 表单签名(policy、x-oss-signature)、动态对象键、大小限制,以及本轮加密使用的 RSA 公钥;
  2. 直接表单 POST 到 OSS:客户端在本地完成归档与加密后,绕过 ZCode 自身应用服务器,通过 HTTP POST 表单把 tar.gz.enc 直接发送到 Aliyun OSS;OSS 随后回调智谱后端,确认快照已经收到。

活动 socket 也印证了这条路径:运行中的 ZCode 进程维持着到 zcode.z.ai IP 端点的持续 HTTPS 连接,同时还连接两个 Aliyun OSS 存储节点。

关键点:私钥只在服务器侧

逆向结果显示,加密实现采用了“信封加密”结构:

keyId: String(i.encryption.key_version),
keyWrapAlgorithm: "rsa-oaep-sha256",
publicKeySpkiPem: Ylt(i.encryption.public_key)
  • 内容使用 AES-256-CTR 和临时对称密钥加密;
  • 该对称密钥再通过 RSA-OAEP-SHA256,用服务器下发的公钥进行包装。

对应的私钥从不落到用户机器。尝试用系统上所有本地私钥解开信封密钥均失败,这也符合预期。换言之,落在本地磁盘上的 313MB 密文,用户打不开,客户端同样打不开;只有智谱后端持有解锁钥匙。

报告据此提出质疑:如果该功能真正服务面向用户的回滚或跨设备同步,解密密钥应当在本地存在,类似 Git 或 Time Machine 的做法。私钥完全由服务器持有,意味着服务器在需要时可以读取这部分代码数据。

被打包的内容:.git 占有效载荷 86.6%

加密文本虽无法读取,但打包过程中生成的文件清单 manifest 以纯文本留在本地。对一个包含 42,411 个文件的快照进行拆解后,内容占比如下:

内容 体积 占比 包含信息
.git/lfs/ 196.1 MB 56.8% 所有已下载的二进制资产和大型媒体
.git/objects/ 102.2 MB 29.6% 完整提交历史对象存储(commit、tree、blob)
.git/logs/ 0.6 MB 0.2% reflogs、本地分支历史与未推送操作痕迹
源代码和文档 约 46.2 MB 13.4% src/、配置文件、内部文档

仅 .git 目录就占有效载荷的 86.6%。上传完成后,云端拿到的并不只是当前工作树,而是从项目第一天起的完整仓库谱系,包括后续提交中已删除的历史 API 密钥和敏感配置、未公开的本地分支名称,以及 .git/config 中记录的内部 GitLab 主机名和仓库路径。

另一个名为 repo_snapshot_extra_manifest 的附加表达式,会对全局 ZCode 配置文件(如 settings.behavior.json)做哈希,并随每次快照与工作区内容一并打包。

界面开关不能真正关闭上传

发现问题后的自然动作是进入设置关闭相关选项。但把界面开关与代码库交叉比对后,结果并不符合用户预期:

开关 用户预期 实际作用
优化体验(optimizeAgentExperience) 禁用远程测量/数据收集 仅控制数据是否被授权用于模型训练;截图与上传仍在运行
备份快照索引 禁用快照功能 仅控制服务器是否索引已上传快照;本地打包与上传仍持续进行

主机组件代码显示,capture/upload 侧车在启动时被无条件实例化;代码中不存在根据用户偏好整体关闭该管道的检查。唯一前提是 token provider 能返回有效 JWT。

结论很直接:只要处于登录状态,这条后台管道就持续活跃,界面设置无法将其关闭。

触发点主要有两类:captureBeforePrompt(每次提示词之前)以及任务完成时以 repo-wiki-update 标记触发。会话日志显示,单个活跃会话最多产生 62 次 capture 事件。

隐私政策如何表述

查看 ZCode 隐私政策,其明确写到会收集“对话期间提交的文本、文件和代码”,这属于向 LLM 提供上下文时的常见表述。但在整份政策、FAQ 与更新日志中,均未明确提到会静默打包并上传整个工作区和完整 Git 历史。

最接近的表述是通用模板话术:“优化计划默认关闭,未经同意输入不会用于训练”。这与实际发现的整仓快照上传并不是同一层面的说明。

防御:删除只是打地鼠,应锁死目录

最初发现 pending 加密包时,直接删除似乎是最快办法。但半小时内,客户端重新捕获了一份新的 313MB 归档,重试计数从 564 增加到 565。上传侧发现文件消失后,只会重新打包一份。手工删除因此只是打地鼠。

更彻底的办法是在文件系统层面设置不可变标志,在内核级拒绝写入:

macOS

# 清空并锁定 checkpoints 目录
rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
chflags uchg ~/.zcode/v2/checkpoints

# 验证:应输出 "Operation not permitted"
touch ~/.zcode/v2/checkpoints/test

Linux

# 清空并锁定 checkpoints 目录
rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
sudo chattr +i ~/.zcode/v2/checkpoints

# 验证:应输出 "Operation not permitted"
touch ~/.zcode/v2/checkpoints/test

影响与恢复

  • 结果:capture 逻辑一旦尝试磁盘 I/O,就会被内核拦截;本地没有产物,上传管道也就没有可发送内容;
  • 代价:“检查点回滚 / 时间线”界面功能将不可用,而该功能本来就以上传代码为前提;普通聊天、自动补全和工具执行不受影响;日志中被吞掉的 I/O 错误没有实质影响;
  • 恢复方法:macOS 执行 chflags nouchg ~/.zcode/v2/checkpoints;Linux 执行 sudo chattr -i ~/.zcode/v2/checkpoints

结语

使用 AI 工具时,模型推理不可避免需要代码上下文,这一点用户在使用前通常可以接受。但本次发现的争议集中在两处。

第一是数据范围。推理过程发送的是与任务相关的上下文;而快照式采集上传的是整个仓库,以及多年积累的 Git 提交历史。

第二是架构姿态。若设计初衷真是面向用户的恢复或同步,解密密钥应归属用户。私钥完全由服务器持有、隐私政策零披露、后台上传无法停止、删除后仍顽固重打包,这些特征组合起来,更接近“采集”,而非用户可控的备份。

工具终归是工具,边界需要用户自己划定。当软件不提供真正关闭入口时,操作系统内核仍可把这条管道锁进笼子。

评论 0

0/500

评论需审核后展示,请文明发言

💬
还没有评论,来说两句

相关阅读