cd /news/ai-tools/an-ai-coding-app-was-silently-upload… · home topics ai-tools article
[ARTICLE · art-135208] src=dev.to ↗ pub= topic=ai-tools verified=true sentiment=↓ negative

An AI Coding App Was Silently Uploading Your Entire Git History: Inside the ZCode Incident

A security researcher known as ferstar documented that ZCode, an AI coding desktop app from Zhipu, silently packaged entire Git repositories—including full commit history and LFS assets—into encrypted archives and attempted to upload them to cloud storage, with one 313MB snapshot retried 564 times. Zhipu confirmed the upload behavior on Sep 18, 2026, and the researcher found the decryption key exists only on Zhipu's servers, meaning users cannot open the archives on their own disks. A smaller 538-file public repository snapshot was accepted by the server, showing at least one upload succeeded.

by read5 min views1 publishedSep 20, 2026

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.

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.

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:

POST /api/v1/snapshot/upload-credential on zcode.z.ai. The server responds with OSS form credentials, a size limit, and an RSA public key. Live network checks matched this picture: the running process held persistent connections to zcode.z.ai plus two Aliyun OSS nodes.

This is standard envelope encryption, and that is exactly the problem.

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.

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) 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:

.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 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:

optimizeAgentExperienceEnabled): only controls whether your data is used for model training. Snapshots continue regardless.repoSnapshotIndexingEnabled): only controls whether the server builds an index after receiving a snapshot. Packaging and up 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.

On Sep 18, hours after the writeup spread, Zhipu published a statement, later covered by outlets including IT Home. Their key points:

The company does not dispute that uploads happened. What remains unverifiable from the outside:

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.

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 up 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

touch ~/.zcode/v2/checkpoints/test

On Linux:

rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
sudo chattr +i ~/.zcode/v2/checkpoints

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.

The pattern here is bigger than one app. Any tool that touches your source code deserves three questions:

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.

── more in #ai-tools 4 stories · sorted by recency
── more on @zcode 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/an-ai-coding-app-was…] indexed:0 read:5min 2026-09-20 ·