← Gautam Parab

ZCode's Off Switches Sat Downstream of the Upload

The discovery started with a nearly full disk. The developer who writes as ferstar owns a base-model MacBook Air with 256 GB of storage, and while clearing space found that ~/.zcode, the data folder of Zhipu’s ZCode desktop coding agent, had grown past 700 MB. Inside was a single encrypted file of 313 MB, sitting in a folder named pending. A small state file next to it recorded 564 failed upload attempts.

They published the findings on 18 September. While they were logged in, ZCode had been packing up their whole workspace, encrypting it, and trying to send it directly to Aliyun object storage. The package included the full .git history, the LFS cache and the reflogs. Zhipu responded that evening: the uploads came from a codebase-indexing feature that had been on by default. It apologized, said the problem was fixed, promised to open-source ZCode, and gave every user an extra weekly quota reset.

The headline version is “coding agent steals code”. That summary misses the mechanism, and I think the mechanism is the more interesting part. The two privacy switches in ZCode’s settings were placed after the step that took the snapshot. The encryption was designed so that the only party able to read the data was the one that received it. Both choices matter well beyond Zhipu.

What was in the box

The archive was encrypted, but ZCode also saved a plaintext manifest of what it had packed: 42,411 files, 345 MB before compression. The project behind it was a commercial repository of about 10 GB, most of which was dependencies that ZCode excluded. The manifest breaks down like this:

What one ZCode workspace snapshot contained, by size Treemap of a 345 MB, 42,411-file workspace snapshot from ZCode client 3.12.3, sized by bytes. The .git/lfs cache is 196.1 MB, 56.8 percent. The .git/objects commit history store is 102.2 MB, 29.6 percent. Source code, config and docs are about 46.2 MB, 13.4 percent. The .git/logs reflogs are 0.6 MB, 0.2 percent, a hairline. Git data together is 86.6 percent of the payload; the working tree an agent reads for context is 13.4 percent. Source: ferstar's manifest breakdown, 18 September 2026. ONE SNAPSHOT · 345 MB · 42,411 FILES · AREA = BYTES · FERSTAR'S MANIFEST, 18 SEP 2026 .git/lfs — 196.1 MB, 56.8% — ferstar, 18 Sep 2026 .git/objects — 102.2 MB, 29.6% — ferstar, 18 Sep 2026 .git/logs (reflogs) — 0.6 MB, 0.2% — ferstar, 18 Sep 2026 Source code, config, docs — about 46.2 MB, 13.4% — ferstar, 18 Sep 2026 .git/lfs · 196.1 MB · 56.8% every large binary asset ever pulled .git/objects · 102.2 MB · 29.6% every commit, tree and blob Source, config, docs · 46.2 MB · 13.4% the working tree an agent reads Filled cells, git history: 86.6% Outlined cell, current files: 13.4% The hairline under .git/objects is .git/logs, the reflogs: 0.6 MB, 0.2%. node_modules and similar folders were excluded by the client.
Of everything ZCode packed, 13.4% was the current state of the project. The other 86.6% was its history.

A coding agent needs the files it is working on, and sometimes it needs a search index over the rest of the tree. It does not need every binary asset the repository has ever downloaded, and it does not need the reflog. The reflog is a local record of where each branch pointer has been, including branches that were never pushed anywhere. Ferstar lists what the history carries that the working tree does not: API keys and configs that later commits deleted, names of unpushed branches (which can reveal plans), and internal hostnames in .git/config.

The first item on that list is the costly one, and it is well measured. GitGuardian’s State of Secrets Sprawl 2026 report, published in March, found at least one hardcoded secret in 32.2% of the internal repositories it scanned. It also tracked valid secrets it had found in public repositories in 2022: four years later, 64% were still active. A secret removed from the working tree is not removed from the history, and a key that stays valid for four years is exposed for that whole time to anyone who holds a copy of the history. That report is six months old and I am using it only as background. The finding it rests on, that git keeps whatever was committed until someone deliberately rewrites history, is a lot older.

What actually left

The headline number needs a correction, and ferstar made it in an update posted the next day. The 313 MB snapshot never uploaded. It was over the server’s size limit, which is why it failed 564 times. Their home router’s connection records show that none of those encrypted chunks left their network. Something smaller did get through: a public repository of 538 files, about 15 KB after compression and encryption, which the server marked as accepted. A user on the forum NodeSeek then reproduced the same folder layout on Windows and found several small workspaces with no failure record, which suggests they uploaded without trouble.

So the proven exfiltration in this story is small. What the evidence establishes is that the pipeline existed and ran. The 313 MB case failed because of the server’s size cap, and no privacy control played any part in stopping it. A smaller project would have gone through.

Where the switches were

ZCode had two settings a worried user might have turned off. Ferstar traced both through the client code of version 3.12.3, and neither one guarded the step that mattered.

ZCode's snapshot pipeline and where its two privacy toggles sat Seven steps in order, as reconstructed from ZCode client 3.12.3. One: the user is signed in with a valid session token. Two: capture triggers before every prompt and on tasks tagged repo-wiki-update. Three: the client packs the workspace, .git included, with node_modules excluded. Four: it encrypts the archive and wraps the key with a public key the server supplies. Five: it uploads directly to Aliyun object storage, which calls back to Zhipu. Six: the server indexes the snapshot for Repo Wiki. Seven: the data is authorized for model training. The Repo Snapshot Indexing toggle gates only step six. The Optimize Experience toggle gates only step seven. No privacy setting reaches steps one to five, and deleting the local archive only triggers a repack. Source: ferstar, 18 and 19 September 2026. ZCODE 3.12.3 · SNAPSHOT PIPELINE · AS TRACED BY FERSTAR, 18–19 SEP 2026 1 Signed in with a valid session token 2 Capture: before every prompt, and on wiki update 3 Pack the workspace, .git included 4 Encrypt; key wrapped to the server's public key 5 Upload straight to Aliyun OSS, callback to Zhipu 6 Server indexes the snapshot (Repo Wiki) 7 Data authorized for model training Toggle: “Repo Snapshot Indexing” Toggle: “Optimize Experience” No privacy setting reaches steps 1–5. The capture sidecar started at login with no preference check. Deleting the archive just triggered a fresh repack. Per the same audit, client 3.14.0 strips the upload pipeline; only local checkpoints remain.
The two switches in ZCode's settings controlled what happened to a snapshot after it had been taken. Nothing in the settings could prevent the snapshot from being taken.

“Optimize Experience” sounds like a telemetry opt-out. According to the code, all it controlled was whether uploaded data could be used for training. “Repo Snapshot Indexing” sounds like the feature itself. It controlled whether the server indexed a snapshot after receiving it. The component that did the capture and upload was started unconditionally when the app launched, and the only thing it checked was that the user had a valid login token. The capture ran before every prompt. A single active session in ferstar’s logs produced up to 62 capture events. When they deleted the pending archive, ZCode made a new one within half an hour.

I think this detail deserves more attention than the file sizes. The settings were real and each did what its code said. They were just attached to the downstream uses of the data (indexing and training) and not to the collection step. A user who switched both off would reasonably think they had opted out, and would still have been uploading. It is consent placed at the wrong stage of the pipeline, and nothing about that design is specific to ZCode.

Researchers have started measuring this category of failure. In June the Singapore and Korea AI Safety Institutes published a joint evaluation of agents on twelve realistic tasks with no attacker involved. Data minimization was one of the five risk types they scored. None of the three agents tested completed every scenario both correctly and safely. The authors note that successful task completion “often coincided with data-handling failures such as accessing unnecessary information.” Their conclusion is that capability and data-handling safety need separate evaluation, and ZCode fits that pattern well: the agent worked fine as a coding tool while it over-collected. A February benchmark, AgentLeak, measured something related in multi-agent systems. It found that audits looking only at final outputs missed 41.7% of privacy violations, because the leaks happened in channels the user never sees. ZCode’s version of that hidden channel was simpler: a background upload running next to the chat window.

The key was never yours

Then there is the encryption. ZCode used standard envelope encryption. Each snapshot was encrypted with a one-time symmetric key, and that key was wrapped with an RSA public key the server sent at upload time. The matching private key stayed on Zhipu’s side. The 313 MB file on ferstar’s disk could not be opened by ferstar or by the ZCode client. Only Zhipu could read it.

That is a sensible design when you want to protect data in transit from third parties. It is a strange design for a feature Zhipu describes as supporting “session checkpoint recovery” and “historical version rollback.” A backup meant for the user would normally be encrypted to a key the user controls, the way a local Time Machine backup or a git clone is readable by its owner. Encrypting to the server’s key means the only party who can inspect what was sent is the one receiving it. It also means the only party who can confirm that it was deleted is the same one.

Zhipu’s statement, as reported by PANews, says that after Wiki pages are generated in the cloud, “the related uploaded data is immediately destroyed and not saved.” That may be true. Nobody outside the company can check it, and it is hard to square with the recovery and rollback features the same statement names, which need stored snapshots to work. Ferstar’s list of unanswered questions starts with this one: how would anyone outside the company prove “destroyed immediately”?

The written policy is thin as well. The ZCode privacy policy, effective 15 June 2026, says the service collects “the text, files … and code submitted to us through conversation.” I read the English version on 19 September. It does not contain the words “snapshot,” “workspace” or “repository.” The operative phrase is “submitted to us through conversation,” and a background snapshot taken before each prompt was never submitted through a conversation.

Other agents upload code too

Sending code to a server is normal for this kind of tool. Every cloud coding agent does it, because the model has to see the code. Anthropic’s data-usage page for Claude Code says plainly of its cloud sessions: “Your repository is cloned to an isolated VM.” The difference is in who starts the transfer and what the documentation says about it. The same page says Claude “accesses the repository where you initiate your Claude Code session,” that usage metrics “never include your code, prompts, or file paths,” and that a session transcript is shared after a feedback survey only if the user explicitly answers yes. A whole-repository upload that the user initiates, and that the documentation describes, is a different situation from a whole-history upload that starts at login and that the documentation leaves out.

This is the second essay I have written about Z.ai this week. The first was about a technical report that named a kernel flag and not the chip it ran on. The two cases look alike to me: the company discloses the parts that are easy to disclose and leaves out the part a reader would most want to check.

The pattern is old. On 1 November 1999, The Register reported that RealNetworks’ free RealJukebox player, which had something like 13 million registered users, was sending the company a record of the CDs each user played, tied to a unique ID assigned at registration. The researcher who found it, Richard Smith, found it much as ferstar did, by checking what the software sent rather than what it said about itself. The scandal then was not the amount of data collected. It was the gap between the software’s behavior and anything the user had been told. Twenty-seven years later a much more capable program opened the same gap.

What would settle it

Zhipu says version 3.14.0 is fixed, and ferstar’s audit of that release supports this: the upload pipeline has been removed from the client, and the endpoint that issued upload credentials now returns 404. Credit where due, that was fast. The open-source release will be useful if it includes the 3.12.3 code that was caught and not only the current, cleaned-up version. A clean current version shows what the client does today. It does not show what earlier versions did. Ferstar also points out that the desktop client can still receive hot updates, so a clean version is a statement about one release, not a guarantee about the next.

I would want three things before I trusted a coding agent with a private repository. The collection switch should come before the capture step, not after. Any snapshot kept for the user’s benefit should be encrypted to a key the user holds. And the privacy policy should describe collection in terms a user would recognize, such as “we upload your git history,” instead of “files submitted through conversation.” ZCode 3.12.3 met none of the three. I suspect few agents are ever checked against the first one, because checking means someone reading the client code, and this time the person who did it was a developer trying to free up disk space.

References

  1. ferstar (2026). Inside ZCode: Silently Uploading Your Entire Git History to the Cloud. Blog post, 18 September 2026; update section dated 19 September 2026.
  2. PANews (2026). Zhipu apologizes over ZCode code-upload incident: will not save user data, will open-source software and reset quotas. 18 September 2026.
  3. ZCode. ZCode Privacy Policy. Version effective 15 June 2026; read 19 September 2026.
  4. Baek, H., Noh, J., Seo, S., Kim, Y., Matienzo, G. W. L. et al. (2026). An Evaluation of Data Leakage Risks in Tool-Using LLM Agents in Realistic Scenarios. arXiv:2606.17114, 15 June 2026 (joint evaluation by the Singapore and Korea AI Safety Institutes).
  5. El Yagoubi, F., Badu-Marfo, G., Al Mallah, R. (2026). AgentLeak: A Benchmark for Internal-Channel Privacy Leakage in Multi-Agent LLM Systems. arXiv:2602.11510, 12 February 2026.
  6. GitGuardian (2026). The State of Secrets Sprawl 2026. Report page; announced 17 March 2026.
  7. Anthropic. Data usage. Claude Code documentation, read 19 September 2026.
  8. John Lettice, The Register (1999). RealNetworks caught secretly swiping users’ jukebox data. 1 November 1999.