ZCode silently uploads full Git history to Aliyun OSS – investigation and mitigation

TL;DR

ZCode silently packages your whole project—including the complete .git history, LFS cache, and local config—encrypts it with a server‑provided RSA public key, and uploads the ciphertext directly to Aliyun OSS. The upload runs whenever you are logged in, cannot be turned off in the UI, and the decryption key resides only on Zhipu’s backend, meaning only the server can read the data.


How the upload works – client‑side steps

The client first obtains a one‑time RSA public key and OSS form credentials from https://zcode.z.ai, then streams a tar‑gz archive through AES‑256‑CTR encryption and RSA‑OAEP key wrapping before posting the encrypted file directly to Aliyun OSS.

sequenceDiagram
    participant C as ZCode client
    participant S as zcode.z.ai
    participant O as Aliyun OSS
    C->>S: POST /api/v1/snapshot/upload-credential
    S-->>C: snapshot_id, RSA public key, OSS policy, signature, object key
    C->>C: tar.gz → AES‑256‑CTR → RSA‑OAEP wrap key
    C->>O: POST multipart/form-data (tar.gz.enc)
    O->>S: callback confirming receipt
  • Stage 1 – Credential request: The client contacts the ZCode coordinator, receives a dynamic object key, size limits, and the RSA public key for that upload session.
  • Stage 2 – Direct OSS upload: After local encryption, the client bypasses ZCode’s own servers and streams the ciphertext (*.enc) straight to Aliyun OSS via a signed form POST. OSS then notifies Zhipu’s backend.

Network captures showed persistent HTTPS connections to both zcode.z.ai and two Aliyun OSS nodes during this process.


Why the encryption is one‑way for the user

The RSA key pair is generated on the server; only the public key is sent to the client, so the client (and the user) can never decrypt the uploaded archive.

{
  "keyId": "String(i.encryption.key_version)",
  "keyWrapAlgorithm": "rsa-oaep-sha256",
  "publicKeySpkiPem": "Ylt(i.encryption.public_key)"
}
  • An ephemeral symmetric key encrypts the tarball with AES‑256‑CTR.
  • The symmetric key is wrapped with the server‑supplied RSA‑OAEP‑SHA256 public key.
  • The private key never touches the user’s machine, so the 300 MB ciphertext on disk is unreadable locally.

"Envelope encryption with server‑held private keys turns local backup into remote asset extraction." – @xcc3641 (HN comment)

If the feature were intended for user‑side restore or sync, the private key would be stored locally, just like Git or Time Machine. Its absence indicates the sole purpose is to let the server read the data at any time.


What is actually uploaded – a breakdown

Approximately 87 % of the uploaded payload is .git data, meaning the server receives the full commit history, LFS objects, reflogs, and even deleted secrets.

Path Size % of payload Contents
.git/lfs/ 196.1 MB 56.8 % All LFS‑cached binaries and media
.git/objects/ 102.2 MB 29.6 % Complete object store (commits, trees, blobs)
.git/logs/ 0.6 MB 0.2 % Reflogs – local branch movements
Source files & docs ~46.2 MB 13.4 % src/, config files, docs

A plain‑text manifest listing every file (42 411 entries) is stored locally, exposing the directory structure even though the archive itself is encrypted.


UI toggles do not stop the upload

Both the "Optimize Experience" and "Repo Snapshot Indexing" switches only affect downstream server‑side processing; the capture‑and‑upload sidecar runs unconditionally at startup.

Switch Intended effect Actual effect
optimizeAgentExperienceEnabled Disable telemetry Still captures and uploads snapshots; only disables model‑training consent
repoSnapshotIndexingEnabled Disable snapshot feature Uploads continue; only stops server‑side indexing of the uploaded data

The client invokes captureBeforePrompt before every LLM prompt and also on task‑completion events labeled repo-wiki-update. A single session can generate dozens of capture events (up to 62 observed).


Privacy policy omission

The official ZCode privacy policy mentions collection of "text, files, and code submitted during conversations" but does not disclose the automatic, full‑workspace snapshot and Git‑history upload. The only related clause is a generic statement that optimization is off by default and data will not be used for training without consent.


Community reaction and official response

  • Several commenters noted similar behavior in other AI agents reading dotfiles and ignored files (e.g., @philbo).
  • @acrispino shared Zhipu’s public apology, claiming the feature was an early‑launch bug tied to a "codebase indexing" function, that uploaded data is destroyed after Wiki generation, and that the issue has been fixed.
  • Other users expressed loss of trust and suggested sandboxing or isolation strategies (see @radio879’s extensive sandbox discussion).

How to stop the uploads on your machine

Deleting the pending archive only triggers a fresh capture; the reliable mitigation is to make the ~/.zcode/v2/checkpoints directory immutable at the filesystem level.

macOS

rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
chflags uchg ~/.zcode/v2/checkpoints   # set immutable flag
# Verify: touch should fail with "Operation not permitted"

Linux

rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
sudo chattr +i ~/.zcode/v2/checkpoints   # set immutable attribute
# Verify: touch should fail with "Operation not permitted"

Impact: The checkpoint/rollback UI will no longer work because it relies on the uploaded snapshots, but normal chat, autocomplete, and tool execution remain functional. To restore the feature, remove the immutable flag (chflags nouchg on macOS or chattr -i on Linux).


Bottom line

  • ZCode automatically captures and encrypts your entire workspace, including full Git history, and uploads it to a cloud bucket.
  • The encryption key is held only by Zhipu’s servers, so only they can decrypt the data.
  • The feature cannot be disabled via UI settings; it runs whenever you are logged in.
  • The privacy policy does not disclose this behavior, raising serious trust and compliance concerns.
  • Users can block the upload locally by making the checkpoints directory immutable, at the cost of losing ZCode’s snapshot‑restore functionality.

"If the Repo Wiki feature generates Wiki pages, it may trigger an upload of repository data. After the Wiki pages are generated in the cloud, the uploaded data is destroyed immediately and is not stored." – Zhipu’s statement quoted by @acrispino

Recommendation: Treat ZCode as a data‑exfiltration risk unless the vendor provides transparent, opt‑in controls and stores decryption keys locally. Consider sandboxing AI agents or using tools with clear, user‑controlled data handling policies.

Sources

Related