Skip to content

Build1 publisher3 min readPublished

ZCode encrypted a 313MB snapshot of a commercial repo to a key only its server holds

The researcher ferstar found the archive sitting in ~/.zcode, 86.6 percent of it .git history, after 564 failed attempts to push it straight to an Aliyun OSS bucket, and Zhipu confirmed the upload behavior the day the writeup went out.

The Engineer · Build desk

Illustration accompanying ZCode encrypted a 313MB snapshot of a commercial repo to a key only its server holds

What happened

  • The researcher known as ferstar published on Sep 18, 2026 that Zhipu's ZCode desktop app packaged whole repositories and uploaded them to cloud storage, and Zhipu confirmed the upload behavior that day.
  • A plain-text state file in the app's checkpoints folder recorded 345,549,173 workspace bytes packed into a 313,070,842-byte encrypted archive labeled baseline, with a failureCount of 564.
  • The locally stored manifest covered 42,411 files, of which .git was 86.6 percent, dominated by 196.1MB of LFS objects and 102.2MB of commit history.
  • Reading the unpacked app.asar bundle, ferstar reconstructed an upload path in which the client posts the encrypted archive straight to Aliyun OSS without passing through Zhipu's own application servers.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Detection has to happen at the network boundary rather than in the tool: the app's logs do not record upload URLs, and the payload leaves to an object-store host that a vendor-domain allowlist permits.
  • exposure Whoever receives one of these snapshots holds commit history, so credentials that a team rotated out years ago are live again in someone else's storage.
  • decision A team that finds one of these archives cannot decrypt it to establish what left, so the evidence has to be gathered before the upload, from directory growth and per-destination byte counts.
  • cost This class of audit is paid for per app and per version by whoever runs egress capture, because each Electron client has to be unpacked before anyone knows where it posts.

Look at the archive that failed. It is the informative one. The local state file records 345,549,173 bytes of workspace going in and 313,070,842 bytes of ciphertext coming out [4]. A tar.gz and an AES pass removed about nine percent [1]. The manifest explains it. LFS objects, commit objects and reflogs account for 298.9MB of the snapshot [2], and those bytes are already compressed. The 538-file public repository that did reach the server was mostly source text, and it went out at roughly 15KB [7].

So the size limit that the credential endpoint hands back [9] kept a commercial project on local disk. By the time ferstar looked the client had tried 564 times [4], and the archive was still on the machine [6].

ferstar calls the scheme standard envelope encryption, and says that is exactly the problem [22]. Contents go under a random AES-256-CTR key, and that key is wrapped with RSA-OAEP-SHA256 using a public key the server supplies at upload time [10]. The private half stays in the cloud. ferstar tried every private key on the machine against the envelope and none of them opened it [14]. As a backup format it has one unusual property: the owner of the data cannot restore from it. According to the writeup, a tool built for crash recovery or cross-device sync would keep the decryption key on the machine, the way Git and Time Machine data does [15].

The transfer path makes this hard to see from the network. The client asks zcode.z.ai for credentials at POST /api/v1/snapshot/upload-credential and gets back OSS form credentials, a size limit and the RSA public key [9]. The bytes then go to Aliyun OSS as an HTTP POST form [11]. OSS calls the backend so the upload gets recorded [12]. A live check on the running process found persistent connections to zcode.z.ai and to two Aliyun OSS nodes [13]. The app's logs did not include upload URLs, and ferstar read the endpoints out of the unpacked app.asar bundle [8].

An allowlist written around a vendor's API domain passes this upload, because the volume goes to an object-store host. The signal available to a defender is bytes per destination, and a data directory that has grown past 700MB [3].

A recipient gets history. Reflogs are only 0.6MB of the manifest, and they record activity on local branches that were never pushed [17]. ferstar lists secrets deleted or overwritten long ago but still present in history, unpushed branch names that expose unreleased product direction, and .git/config, which often carries internal GitLab hostnames and repository paths [19]. The client also builds a repo_snapshot_extra_manifest that hashes the global ZCode settings file and bundles it across workspaces into every snapshot [20]. The project totaled 10GB, and ferstar describes almost everything in the snapshot, once dependencies are excluded, as core intellectual property [5].

This rests on one researcher, one machine and one commercial project, plus Zhipu's confirmation of the upload behavior on the day of publication [2]. ferstar mapped each settings toggle against the code; the text available here stops before that mapping, so what switching snapshots off actually prevents is still unknown. The method transfers whether or not other clients behave this way: unpack the Electron bundle, read the upload code, then watch the data directory and the destination hosts.

What to watch

  • Whether Zhipu's confirmation turns into a change anyone can verify, in particular whether the RSA private key moves to the client or snapshots become opt-in.
  • Whether other Electron-based AI coding clients use the same credential-then-object-store upload path, which unpacking their app.asar would show.
  • Whether the 15KB snapshot the server accepted has a stated retention or deletion path on the OSS side.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories