Z.ai's ZCode uploads full Git histories without a working opt-out, researchers find

Reverse engineering found encrypted workspace snapshots sent to Aliyun OSS whenever a developer uses the logged-in desktop app.

By · Published

Primary source: X - J. F. Zhang

Why it matters

Coding agents receive broad filesystem access. Undisclosed full-repository snapshots turn that access into a source-code governance risk for developers and employers.

A laptop screen displaying code with glowing data streams silently rising from it in a dimly lit workspace, hinting at hidden data transfer.

J. F. Zhang (@ferstar_org) reported on September 18th that Z.ai's ZCode desktop app packages developers' workspaces, including complete Git histories, and uploads the encrypted snapshots to Alibaba Cloud's Object Storage Service without a working user opt-out.

Zhang found the pipeline while clearing space from a MacBook with 256GB of storage. The local ~/.zcode directory had grown past 700MB, including a 313MB encrypted file under ZCode's checkpoints directory. In a forensic account published alongside his thread on X, Zhang said the archive represented a 345MB snapshot of an active commercial project and had accumulated 564 failed upload attempts.

The finding concerns ZCode, the desktop coding environment that Z.ai (@Zai_org) distributes as the official development interface for its GLM models. ZCode can read and edit repositories, operate a terminal and run longer software-development tasks across macOS, Windows and Linux. That access makes the scope and visibility of its data transfers a material security issue for developers working with private source code.

What the client sends

Zhang reverse-engineered ZCode's packaged Electron application and reconstructed a two-step upload process. The client first requests credentials from zcode.z.ai through an endpoint named /api/v1/snapshot/upload-credential. Z.ai's server returns a snapshot identifier, storage credentials, size limits and an RSA public key. ZCode then creates a compressed archive, encrypts it locally with AES-256-CTR, wraps that key using RSA-OAEP-SHA256 and posts the encrypted file directly to Aliyun OSS. Alibaba's storage service subsequently calls back to Z.ai's backend to register the snapshot.

The server supplies the public key used for each encryption operation. Zhang found no corresponding private key on the device and could not decrypt the local archive with keys available on his system. The architecture therefore protects the transfer from third parties while leaving Z.ai's backend able to decrypt the uploaded source code.

A plaintext local manifest allowed Zhang to inspect the archive's contents without decrypting it. In a snapshot containing 42,411 files, the .git directory accounted for 86.6% of the payload. Git Large File Storage objects represented 196.1MB, the repository object store used for commit history accounted for 102.2MB, and Git logs added another 0.6MB. Current source code and documentation made up about 46.2MB.

That scope reaches beyond the files visible in a developer's current working tree. Git objects can retain deleted credentials and earlier versions of sensitive configuration files. Reflogs and local references can expose unpushed branches, while .git/config can contain internal hostnames and repository paths. Zhang also found a separate manifest covering global ZCode configuration files.

The switches do not stop collection

ZCode includes settings labeled "Optimize Experience" and "Repo Snapshot Indexing." Zhang's review of the client code found that the first controls whether inputs may be used for model training and the second controls server-side indexing. Neither prevents ZCode from creating or uploading snapshots.

According to Zhang, the capture component starts whenever the client can obtain a valid login token. Snapshot events appeared before prompts and after tasks associated with repository wiki updates, with as many as 62 capture events recorded during one session. Deleting the pending archive did not end the process: ZCode created another 313MB package within half an hour and resumed its retry counter.

A separate report filed in Z.ai's public feedback repository corroborated the behavior on ZCode 3.12.3 for macOS and version 3.10.0 on Linux. That user found accepted snapshot records even with repoSnapshotIndexingEnabled set to false, including one manifest with more than 2,000 .git paths and another pending encrypted archive approaching 1GB. Z.ai's repository assigned the report its standard P2 priority label.

Version 3.12.3 was released on September 17th, one day before the disclosures. Its release notes list model-management changes, workspace plugins and connection fixes, with no change addressing repository snapshots or their controls.

The privacy-policy gap

ZCode's privacy policy states that the service collects text, files and code that users submit through conversations. It describes those submissions as discretionary and says permissions for additional features will be requested when the relevant feature is triggered. The policy does not describe automatic full-workspace snapshots, Git object databases, reflogs or repeated background uploads tied to login status.

Zhang recommends preventing ZCode from writing to its checkpoints directory at the operating-system level. His workaround deletes the existing local checkpoint files, recreates the directory and marks it immutable on macOS or Linux. That blocks new snapshot artifacts from being generated, though it also disables ZCode's checkpoint rollback and timeline functions. Developers handling private repositories have a simpler immediate boundary: keep ZCode signed out or remove it from machines containing source code that cannot be sent to Z.ai's servers.

Reader comments

Conversation for this story loads after sign-in.