跳到主要内容

我们只拿你亲手交过来的东西

· 阅读需 7 分钟
Creator of UnDercontrol

我们只拿你亲手交过来的东西

9 月 18 日,有人逆向了智谱的桌面编程助手 ZCode,发现它在用户登录后会把整个工作区在后台打包上传。不只是你正在改的代码,还有完整的 Git 提交历史、LFS 缓存和本地分支记录。被曝光的那个商业项目,一次快照约 313MB、四万两千个文件,.git 占了 86.6%。

当天下午智谱道歉,说问题出在「代码库索引」功能上线初期默认开启,已经修复,并承诺开源客户端、请第三方审查、给所有用户补一次周额度。回应的速度和态度都不差。

但这件事里还有两层,比「默认开启」更要紧。

差额​

你打开一个编程助手,心里交出去的是什么?是「这个文件」「这段报错」「这个函数怎么改」。ZCode 拿走的是整个仓库从第一次提交到现在的全部历史:包括你早就删掉的那次误提交,包括那个你后来 force push 掉、reflog 里还留着的分支,包括曾经写在配置里、后来才移走的那个密钥。

问题不在「数据上没上服务器」,任何云端工具的数据都在服务器上。问题在你交出去的那一小块,和被拿走的那一整块,中间差了多少。

开关​

比默认开启更严重的是第二件事。

ZCode 的设置里有两个看起来相关的开关。据逆向分析,「体验优化」控制的是数据要不要用于训练模型,「仓库快照索引」控制的是服务端要不要给已经上传的快照建索引。两个都关掉,本地打包和上传照旧发生。有用户拿全关状态复现,机器上照样躺着一个 699MB 的本地快照和一串上传日志。

默认开启是个决策失误,能改。开关名字看起来在管这件事、实际管的是别的事,性质不一样:用户做了正确的动作,拿到了错误的结果,而且他不会知道,他以为自己已经处理完了。

第三件:私钥只在服务端。内容用 AES 加密,密钥再用 RSA 公钥包起来,解密私钥不下发。就算你在本机抓到了那个上传的包,你也打不开它,没法自查被传走的到底是什么。智谱说上传的数据「会立即销毁,不会保存」,这句话可能是真的,但用户手上没有任何办法核实它。

我们这边​

UnDercontrol 把任务、笔记、账目、资料放在一套数据里,同一个 Markdown 编辑器用在所有这些地方,CLI、桌面端、Web、iOS 面对的是同一份内容。这样一个东西谈隐私,只说「我们很重视」没有意义。下面是具体的。

我们从不扫描你的磁盘。 ud CLI 出厂的二进制里,git 这个命令被调用的次数是 0。它不读 .git,不打快照,不建索引,没有任何后台上传。整个 CLI 里遍历目录的地方只有四处,每一处的路径都是你给的:ud pull / ud push 你点名的那个目录、ud sync resource <目录>、装 skill 包的时候、以及它自己的状态目录。其中 ud sync 会跳过所有以 . 开头的文件和目录,也就是说即使你主动让它同步一个仓库,.git 也根本不会被枚举。

Agent 状态回报只有一个词。 你在 UnDercontrol 里跑 Claude Code、Codex、OpenCode 或者任何终端里的 agent,我们装的 hook 会告诉服务器这个会话现在是什么状态。发过去的整个请求体是一个字段,取值是 pending、running、planning、awaiting_input、stopped、exited 六个词之一。hook 脚本确实读到了 agent 给它的工作目录路径,但那个路径只在你本机用来匹配一个本地文件,不进请求体。

三个客户端里没有埋点 SDK。 Web、桌面端、iOS 的依赖清单里,sentry、posthog、mixpanel、amplitude、segment、firebase、bugsnag、datadog,一个都没有。网页的 HTML 里也没有任何第三方脚本。

桌面端不后台联网。 检查更新是你点了才发的一次 GET,拉 CDN 上一个静态文件比对版本号,不自动下载、不自动安装、请求没有 body。桌面端源码里出现过的外部域名一共四类:你自己配的 API 地址、那个发版清单、文档站、以及你主动选择安装某个 agent CLI 时它官方的安装脚本。

这些话你可以自己验​

上面每一条,你都没有理由因为我写了就相信。

我们没有一条你读不到的通道。客户端发出的每一个请求都走你配置的那个地址:Web 端在设置里改 custom_api_url,CLI 用 UD_API_URL 环境变量或者 ud config set-context。把它指向你自己的代理,每一个字节你都能看见。我们没有第二个硬编码的上报地址,也没有只有服务端能解开的加密层,而这正是 ZCode 那件事里用户最缺的东西。

会离开你机器的地方,一共五个​

说完不拿什么,得说拿什么。列全了,前面那句才有意义。

  1. 云端同步本身。 用我们托管的后端,你写的任务、笔记、评论、账目和上传的附件就在服务器上。这是这个产品在做的事。
  2. AI 功能。 用 AI 的时候,你提交的内容会发给模型厂商。分两种:配了你自己的 Provider,浏览器直连厂商 API,对话内容不经过我们的后端;用我们云端预置的 Provider 时,密钥不能下发到浏览器,所以经我们后端转发。图片建任务、账单识别、任务摘要这些服务端功能始终走后端。我们不拿你的内容训练任何模型;厂商收到之后怎么处理,按那家自己的条款。
  3. Workspace 远程终端。 通过 UnDercontrol 跑 agent 会话时,终端输出会发到服务器,这样你在手机上或者另一台机器上也能看见它跑到哪了。这份输出只活在服务端的进程内存里,进程一重启就没,从不写进数据库,当初这么设计的理由写在代码注释里:终端的最后几屏经常有密码和部署脚本。真正落库的只有会话元数据:工作目录路径、启动命令、你输入的第一个 prompt、状态、进程号。
  4. iOS 语音输入。 用的是系统自带的语音识别。音频不会发给我们,我们只收到转好的文字,但按 Apple Speech framework 的默认设置,音频会发到 Apple 的服务器做识别。
  5. 官网的访问统计。 udctl.com 装了 Google Analytics,开了 IP 匿名,用来数页面浏览量。App 里没有,任何地方都没有广告和跨站跟踪。

不想让它离开,有两条路​

桌面端本地库:数据存在你自己的机器上,不碰任何服务器。

自托管:跑你自己的实例,数据在你自己的基础设施里。这时候「什么东西离开了我的机器」这个问题,你查自己的日志就有答案。

这两条一直都在,不是为了这篇文章才加的。

完整策略​

在 udctl.com/privacy。里面写了我们存什么、谁会碰到、留多久、怎么删,也写了删号之后审计日志为什么不跟着删:当事人自己能抹掉的审计日志不叫审计日志。

这篇里哪一条你想自己验一遍,把 API 地址指向你的代理跑一天。跟我写的不一样,写信告诉我。