If you use an AI coding assistant, you already accept that it sees the code in your current task. What you probably do not expect is for the app to package your entire repository, including every commit you have ever made, and upload it to cloud storage without asking.
That is what a security researcher known as ferstar documented about ZCode, an AI coding desktop app from Zhipu. The full writeup was published on Sep 18, 2026, and the company confirmed the upload behavior the same day. This article walks through what the investigation found, why the details matter, and what you can do about similar behavior in any tool.
All facts below come from the original writeup at blog.ferstar.org and Zhipu's public response.
It started with 700MB of disk space
The researcher noticed the ~/.zcode data directory had grown past 700MB. Inside it, one file stood out: a 313MB encrypted .enc archive sitting in a checkpoints folder, next to a small state file:
{
"workspacePath": "/Users/ferstar/myprojects/<a commercial project>",
"lastCompressedSize": {
"encryptedSizeBytes": 313070842,
"workspaceSizeBytes": 345549173
},
"kind": "baseline",
"failureCount": 564
}
Translation: the client had scanned an active commercial project, packed 345MB of it into a 313MB encrypted archive labeled baseline (a full snapshot), and had already tried to upload it 564 times. The project totaled 10GB, but after excluding dependencies, almost everything in that snapshot was core intellectual property.
In this case the big archive never made it out. It kept failing because of its size. But a smaller workspace, 538 files from a public repository, was compressed down to roughly 15KB and shows a status of accepted by the server. So at least one snapshot did reach the cloud.
Reconstructing the upload pipeline
The app logs contained no upload URLs, so the researcher unpacked the Electron client's app.asar bundle and read the code. The reconstructed flow works like this:
- The client calls
POST /api/v1/snapshot/upload-credentialonzcode.z.ai. The server responds with OSS form credentials, a size limit, and an RSA public key. - The client packs the workspace into a tar.gz archive, encrypts it with AES-256-CTR, and wraps the symmetric key with RSA-OAEP-SHA256 using that public key.
- The client uploads the encrypted archive directly to Aliyun OSS via an HTTP POST form. The traffic never passes through Zhipu's own application servers.
- OSS fires a callback so the backend can record the upload.
Live network checks matched this picture: the running process held persistent connections to zcode.z.ai plus two Aliyun OSS nodes.
The most damning detail: the private key lives on the server
This is standard envelope encryption, and that is exactly the problem.
- File contents are encrypted with a random symmetric key using AES-256-CTR.
- That symmetric key is wrapped with RSA-OAEP-SHA256 using a public key handed over by the server at upload time.
The private key exists only in the cloud. The researcher tried every local private key on the machine against the envelope and all attempts failed. The 313MB ciphertext on your own disk cannot be opened by you, and it cannot be opened by the ZCode client either. Only Zhipu's backend holds the key.
If the goal were crash recovery or cross-device sync for your benefit, the decryption key would live on your machine, the way Git or Time Machine data does. A key that only the server can open serves one purpose: making sure the server can read everything.
What was actually in the snapshot: 86.6% Git history
The encrypted payload is unreadable, but the snapshot manifest is stored locally in plain text. The researcher tallied a manifest covering 42,411 files:
-
.git/lfs/: 196.1MB, 56.8% (every large binary asset ever pulled through history) -
.git/objects/: 102.2MB, 29.6% (the complete commit, tree, and blob history) -
.git/logs/: 0.6MB, 0.2% (reflogs, including unpushed local branch activity) - Remaining source and docs: about 46.2MB, 13.4%
The .git directory alone made up 86.6% of the snapshot. If that archive reaches the cloud, the recipient gets far more than your current working files:
- Old secrets and configuration that were deleted or overwritten long ago but still exist in history
- Names of local branches never pushed, which expose unreleased product direction
-
.git/config, which often contains internal GitLab hostnames and repository paths
On top of that, the code contained a repo_snapshot_extra_manifest that hashes your global ZCode settings file and bundles it across workspaces into every snapshot.
The settings that do not stop it
The natural reaction is to open settings and switch things off. The researcher mapped each toggle to the code and found neither one touches the upload:
- Optimize experience (
optimizeAgentExperienceEnabled): only controls whether your data is used for model training. Snapshots continue regardless. - Repository snapshot indexing (
repoSnapshotIndexingEnabled): only controls whether the server builds an index after receiving a snapshot. Packaging and uploading continue regardless.
In the client code, the snapshot sidecar is instantiated unconditionally at startup. There is no check against user configuration. The only requirement is a valid login token. Once you are signed in, capture happens before every prompt you send, and one active session produced up to 62 snapshot captures.
The company's response
On Sep 18, hours after the writeup spread, Zhipu published a statement, later covered by outlets including IT Home. Their key points:
- The uploads were tied to a "Repo Wiki" feature for local indexing and session checkpoints.
- Wiki generation "may" trigger repository data uploads, and the data is "immediately destroyed" after the wiki is generated.
- The feature was on by default in its early days and is "already fixed".
- ZCode will be open sourced with third-party audits, and all users get a weekly quota reset.
The company does not dispute that uploads happened. What remains unverifiable from the outside:
- How "immediate destruction" can be proven, and who holds decryption rights over data already uploaded.
- At least one small snapshot was confirmed received by the server, so stored data exists.
- The researcher notes an internal contradiction: checkpoints need stored data to enable rollback, yet the statement claims nothing is kept.
- Whether the promised open source release will include the old upload code, or only the cleaned-up version.
The latest client version, 3.14.0, has physically removed the upload pipeline, and the credential endpoint now returns 404. Version 3.12.3, the one caught in the investigation, had the full pipeline active.
Defense: stop deleting, start locking
Deleting the pending archive did not work. Within half an hour the client generated a fresh 313MB snapshot and the failure counter moved from 564 to 565. The uploader notices the missing file and simply repacks. Manual deletion is whack-a-mole.
The reliable fix is an immutable lock at the filesystem level, so the kernel itself refuses writes to the snapshot directory.
On macOS:
rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
chflags uchg ~/.zcode/v2/checkpoints
# Verify: this should fail with Operation not permitted
touch ~/.zcode/v2/checkpoints/test
On Linux:
rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
sudo chattr +i ~/.zcode/v2/checkpoints
# Verify: this should fail with Operation not permitted
touch ~/.zcode/v2/checkpoints/test
What this costs you: the checkpoint rollback and timeline features stop working, features that were paid for with full code uploads anyway. Normal completion, chat, and tool calls are unaffected. To undo it, run chflags nouchg (macOS) or sudo chattr -i (Linux) on the directory.
The researcher keeps this lock in place even after the fix, because a client with hot-update capability could bring the pipeline back at any time. It works as a tripwire: if writes to the directory suddenly succeed, something changed.
What this incident teaches you
The pattern here is bigger than one app. Any tool that touches your source code deserves three questions:
- What leaves the machine? Task-scoped context is expected. Full repository snapshots with complete history are a different category entirely.
- Who holds the keys? If the vendor encrypts data but keeps the only decryption key in their cloud, the encryption protects them, not you.
- Can you actually turn it off? A toggle that controls training data while the pipeline itself runs unconditionally is not a consent switch.
Audit what a tool uploads before you trust it with a commercial repository. Check its data directory for unexpected growth, watch its network connections, and when a vendor's statement says data is "immediately destroyed", remember that you have no way to verify it. Red lines are yours to draw, and a filesystem lock is sometimes the only switch that works.
Top comments (2)
"Who holds the keys?" is the question that should be on every security review checklist for AI tooling. Server-side envelope encryption that only the vendor can decrypt is marketing-grade encryption — it protects the vendor from transit liability, not you from the vendor. If the decryption key lives exclusively in their cloud, the encryption is the opposite of a privacy guarantee.
The 86.6% being .git history is the detail that makes this genuinely serious rather than just annoying. Deleted secrets, local branch names exposing unreleased feature work, .git/config with internal hostnames — that's not context for a coding assistant, that's a full intelligence picture of a project. Current working files is one category; full commit history with every object ever packed is a completely different one.
The immutable directory lock as a tripwire is the practical takeaway — if writes succeed after you've set chflags/chattr, something in the client changed. That's actually a useful behavioral signal regardless of whether you trust the vendor's stated fix.
We audit outbound connections for any tool we deploy at Black Label. Most AI coding tools are vague about what context they ship. ferstar's methodology of reading the unpacked app.asar and correlating with live network traffic is the right way to find out.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.