ZCode 静默上传你整个 Git 仓库:加密不等于安全,密钥在谁手里才算数?

0 阅读10分钟

ZCode 静默上传你整个 Git 仓库:加密不等于安全,密钥在谁手里才算数?

如果你装了智谱的 AI 编程助手 ZCode,你的代码可能在你毫不知情的情况下被打包传到了它的云端——不是几行代码,而是整个 Git 仓库:LFS 大文件、历史提交、甚至疑似 .ssh 密钥和 .env 环境变量。

9 月 18 日,开发者 ferstar 在清理磁盘时发现,~/.zcode 隐藏目录下躺着一个约 313MB 的加密文件包,旁边还有一份 42,411 个文件的明文清单。顺着清单往下翻,越来越多开发者意识到:这不是个例,而是一整套「静默上传」机制的冰山一角。

这件事的争议点不在「AI 工具收集数据」——业界早就默认了。争议在于三点:没问过我传的是整个仓库而不是代码密钥类文件疑似也在其中。加密在这里不解决信任问题,只解决「事后查不出问题」的问题。

一、72 小时:从一张磁盘清单开始

我按公开报道的时间线把事件捋了一遍,先看全貌:

时间关键节点事实
9/18 上午曝光开发者发现 ~/.zcode 下约 313MB 加密包 + 42,411 个文件明文清单,.ssh 密钥与 .env 疑似在列
9/18 下午智谱首次回应公开致歉,称「Repo Wiki」默认开启导致上传,已修复;承诺开源并接受第三方审计
9/21开源与审计ZCode 正式开源;信通院、绿盟科技审计确认 zcode-prod 桶零数据;v3.14.0 移除 RepoWiki 上传链路;MaaS 上线「数据内容不留存」

ZCode 事件 72 小时时间线

这里最值得注意的不是「传了」,而是节奏:曝光当天道歉、第三天就给出「第三方审计零数据」的结论。对于一次涉及上万开发者代码文件的泄露传闻,这个公关速度不算慢;但对用户来说,「云端零数据」验证的是结果,验证不了传输过程中的拷贝是否被留存——这两件事在技术上完全不同。

二、技术链路:一次「合规」的静默上传

根据开发者公开的逆向分析,ZCode 客户端本地的上传链路是这样的:

  1. 客户端向 zcode.z.ai 请求上传凭证,同时下发 RSA 公钥
  2. 本地用 AES-256-CTR 加密打包数据,再用 RSA-OAEP 包裹 AES 密钥;
  3. 加密包直传阿里云 OSS 桶;
  4. OSS 回调登记上传事件,云端完成登记后解密私钥只在云端

链路本身设计得很严谨:数据流全程加密、私钥不下发客户端、直传对象存储不经中转。技术设计越严谨,「静默上传」这个行为就越显得刻意——这是一套为「合规传输敏感数据」设计的生产级链路,而不是什么误触发的缺陷。

而且它不是一次性行为。逆向数据显示:客户端上传失败后自动重试了 564 次,单个会话最多捕获 62 次上传动作。显然,这不是某个开关的偶发副作用,而是有一套完整的上传状态机在兜底。

三、上传了什么:Git 历史占 86.6%,源码只有 13.4%

最扎心的是清单本身。42,411 个文件里,按体积拆开看:

ZCode 打包内容构成

  • .git/lfs(LFS 大文件缓存):56.8% ——通常是设计稿、数据包、二进制资产;
  • .git/objects(提交对象与历史版本):29.6% ——你的每一次 commit,包含中间状态和已删除的敏感内容;
  • 源码与文档:13.4%
  • .git 其他(logs、分支引用):0.2%。

Git 历史合计占了打包内容的 86.6%。这意味着什么?意味着被上传的最有价值的部分,不是「你现在的代码」,而是你删掉的、改过的、临时提交过的所有历史——包括你可能已经移除的密钥、.env、数据库备份。Git 历史不删就永远在,而 git filter-branch / git filter-repo 重写历史这件事,绝大多数开发者根本没做过。

更值得注意的是过滤逻辑的顺序缺陷:逆向分析显示,历史记录目录的放行判断先于 PEM/KEY 密钥过滤与 1MB 体积限制。也就是说,如果一个文件名同时落在「历史记录目录」和「密钥黑名单」里,放行逻辑会先命中。密钥过滤形同虚设——这能解释为什么 .ssh.env 会出现在清单里。

四、加密≠安全:三个问题比密钥算法更重要

网上很多讨论在辩「AES-256-CTR + RSA-OAEP 够不够安全」,这是跑偏了。传输加密解决的是「链路窃听」,而事件的核心争议是信任边界。三个问题,每个都比密码学强度更要命:

  1. 密钥在谁手里? 解密私钥只存在云端。也就是说:无论本地怎么加密,智谱云端都保留了解开一切的能力。安全意义上的「你的数据」在离开你机器的那一刻,就已经不是你的了。
  2. 用户有没有知情同意? 事件的爆发点恰恰是「Repo Wiki 默认开启」。默认开启 = 用户在安装时从未主动选择过上传粒度。争议文章里流行一句话:「免费 3 亿 tokens 换你整个仓库」——话难听,逻辑没错。
  3. 「未检测到存储」能证明什么? 官方结论说审计确认云端零数据。但审计验证的是「最终没有留存」,验证不了「传输过程有没有被第三方截获、日志有没有记录、样本有没有用于训练」。这两个问题的性质完全不同。

拿行业里另外两个「AI 工具收集数据」案例放一起看,争议的核心就更清楚了:

工具行为用户可见度官方说法
ZCode静默打包整个 Git 仓库上传云端(含密钥疑似)不可见,默认开启已修复、开源、第三方审计零数据
Grok Build将整个项目打包上传 Google Cloud(含用户禁止读取的文件)项目上传时有可见进度社区曝光后聚焦「权限边界」讨论
Claude Code本地回传位置/身份等元信息不可见Anthropic 工程师承认「是有意的实验」

三起事件有一个共同模式:AI 时代,工具默认「为你做更多」,而「更多」的边界是由厂商单方面划定的。用户协议写了 100 页,不如一次默认开启的勾选框说明问题。

五、两分钟自查:你的仓库有没有暴露面

不管你是 ZCode 用户、其他 AI 编程工具用户,还是只用 Git 的开发者,这套自查都值得跑一遍,估算「如果打包发生,会被带走什么」:

# 1. 看看本地有没有这类工具的隐藏数据目录
ls -d ~/.zcode ~/.zcode_data ~/.continue ~/.codex 2>/dev/null

# 2. 这些目录到底占多大(别小看,313MB 就是这么来的)
du -sh ~/.zcode 2>/dev/null

# 3. 敏感文件是否「意外」躺在会被打包的目录里
find ~ -name '*.pem' -o -name '*.key' -o -name '.env' 2>/dev/null | head -20

# 4. 你的 .git 有多大(Git 历史 = 打包大头)
du -sh .git 2>/dev/null

更系统的做法,我写了一个只读的暴露面评估脚本,不读取任何文件内容,只统计文件名与体积:

#!/usr/bin/env python3
"""audit_pack_surface.py — 评估目录中会被「整仓打包上传」波及的暴露面
用法: python3 audit_pack_surface.py [--path 目录]
只统计文件名与体积,不读取任何文件内容。
"""
import argparse
import os
from pathlib import Path

SENSITIVE_SUFFIX = {".env", ".pem", ".key", ".p12", ".pfx"}
SENSITIVE_NAMES = {"id_rsa", "id_ed25519", "id_ecdsa", ".netrc", ".npmrc"}
SUSPICIOUS_DIRS = {".zcode", ".zcode_data", ".continue", ".codex", ".cursor"}


def human(n: int) -> str:
    for unit in ("B", "KB", "MB", "GB"):
        if n < 1024:
            return f"{n:.1f}{unit}"
        n /= 1024
    return f"{n:.1f}TB"


def dir_size(p: Path) -> int:
    total = 0
    try:
        for root, _dirs, files in os.walk(p):
            for f in files:
                try:
                    total += os.path.getsize(Path(root) / f)
                except OSError:
                    pass
    except OSError:
        pass
    return total


def main() -> None:
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("--path", default=".")
    args = ap.parse_args()

    root = Path(args.path).expanduser().resolve()
    if not root.is_dir():
        print(f"路径不存在: {root}")
        return

    git_dir = root / ".git"
    git_size = dir_size(git_dir) if git_dir.is_dir() else 0
    print(f"[1] .git 体积: {human(git_size)}")
    if not git_dir.is_dir():
        print("    当前目录不是 Git 仓库,跳过后续 .git 分析")

    print("\n[2] 高危敏感文件(打包即泄露):")
    hits = 0
    skipped_git = False
    for p in root.rglob("*"):
        if not p.is_file():
            continue
        try:
            rel = p.relative_to(git_dir)
            skipped_git = True
            continue
        except ValueError:
            pass
        if p.suffix.lower() in SENSITIVE_SUFFIX or p.name in SENSITIVE_NAMES:
            try:
                size = p.stat().st_size
            except OSError:
                continue
            print(f"    {p.relative_to(root)}  {human(size)}")
            hits += 1
    if skipped_git:
        print("    (已跳过 .git 内部文件;若同步逻辑未排除 .git,历史本身就会被打包)")
    print(f"    共 {hits} 个高危文件")

    print("\n[3] 已知数据收集类产品目录:")
    for d in SUSPICIOUS_DIRS:
        p = root / d
        if p.is_dir():
            print(f"    {d}: {human(dir_size(p))}")
    print("\n完成。以上仅为暴露面提示,不构成安全结论。")


if __name__ == "__main__":
    main()

诊断结果里,.git 体积越大的项目越要注意——你觉得自己只分享了「代码」,实际上分享的是整个项目的历史。历史里有没有曾经提交又删除的密码、一次性密钥、云服务 AccessKey,你自己可能都不记得了。

六、智谱回应之后,还剩哪些问题没回答

复盘整个事件,智谱的回应质量在一众中国 AI 公司里算不错:道歉及时、开源承诺兑现(9/21 仓库确实公开了)、第三方审计进场。但抛开「事件本身」,行业层面的问号一个都没消失:

  1. Repo Wiki 这个功能的价值是什么? 一个向 AI 编程工具上传「仓库 Wiki」的功能,为什么会触发「整个 .git 目录 + 工作区全部文件」的打包?如果上传的真是 Wiki,打包内容里 86.6% 的 Git 历史怎么解释?
  2. 「默认开启」的开关,设计时有没有人评审过? 任何涉及出站数据传输的功能,默认值应该是「关」——这个共识在安全行业存在几十年了,为什么 AI 工具时代会倒退?
  3. 审计覆盖了存储,覆盖了训练吗? 信通院与绿盟科技确认 zcode-prod 桶零数据,很好。但公开信息没有说明:上传样本是否进入过任何训练集、中间态缓存、日志系统。「没存下来」和「没用过」是两个需要分别回答的问题。

七、结语:Agent 时代缺的不是加密,是数据审计规则

ZCode 事件最值得记住的,不是「智谱干了件坏事然后道歉」,而是它给整个 Agent 生态立了一个反面教材:

当工具被设计成「自作主张」时,技术越严谨,用户越危险。

链路加密、直传 OSS、云端密钥——这套东西放在「用户主动上传文件」的场景里是教科书级的传输方案;放在「用户不知道自己在被上传」的场景里,就变成了教科书级的瞒报方案。同样的技术,信任模型完全不同。

给所有用 AI 编程工具(包括但不限于 ZCode、Copilot、Cursor、各种国产 Agent)的开发者三条实际建议:

  1. 安装任何 AI 工具时,先去找「上传」「数据」「遥测」相关的开关,默认全关,用到再开;
  2. 敏感项目(含密钥、客户数据、未公开商业计划)用隔离环境开发,别让 Agent 工具看到整个工作区;
  3. 定期用上面的脚本扫一次暴露面,把 .git 历史里的敏感内容清掉——git filter-repo 不是高深课程,是基本功。

AI 编程工具会越来越强,能力边界也会越来越宽。「它默认做了什么」这件事,值得每个开发者在下一次 npm install 之前,花两分钟搞清楚。


本文事实均来自公开报道与开发者公开的逆向分析(新浪科技、IT之家、中国经营报及 GitHub 公开讨论,2026-09-18 至 09-21),自查脚本为通用安全工具,与本事件无直接归属关系。