{"slug": "an-ai-coding-app-was-silently-uploading-your-entire-git-history-inside-the-zcode", "title": "An AI Coding App Was Silently Uploading Your Entire Git History: Inside the ZCode Incident", "summary": "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.", "body_md": "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.\n\nThat 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.\n\nAll facts below come from the original writeup at [blog.ferstar.org](https://blog.ferstar.org/en/posts/zcode-silent-workspace-snapshot-upload/) and Zhipu's public response.\n\nThe 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:\n\n```\n{\n  \"workspacePath\": \"/Users/ferstar/myprojects/<a commercial project>\",\n  \"lastCompressedSize\": {\n    \"encryptedSizeBytes\": 313070842,\n    \"workspaceSizeBytes\": 345549173\n  },\n  \"kind\": \"baseline\",\n  \"failureCount\": 564\n}\n```\n\nTranslation: 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.\n\nIn 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.\n\nThe 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:\n\n`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.\nLive network checks matched this picture: the running process held persistent connections to `zcode.z.ai` plus two Aliyun OSS nodes.\n\nThis is standard envelope encryption, and that is exactly the problem.\n\nThe 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.\n\nIf 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.\n\nThe encrypted payload is unreadable, but the snapshot manifest is stored locally in plain text. The researcher tallied a manifest covering 42,411 files:\n\n`.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)\nThe `.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:\n\n`.git/config`, which often contains internal GitLab hostnames and repository paths\nOn 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.\n\nThe 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:\n\n`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 uploading continue regardless.\nIn 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.\n\nOn Sep 18, hours after the writeup spread, Zhipu published a statement, later covered by outlets including IT Home. Their key points:\n\nThe company does not dispute that uploads happened. What remains unverifiable from the outside:\n\nThe 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.\n\nDeleting 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.\n\nThe reliable fix is an immutable lock at the filesystem level, so the kernel itself refuses writes to the snapshot directory.\n\nOn macOS:\n\n```\nrm -rf ~/.zcode/v2/checkpoints\nmkdir -p ~/.zcode/v2/checkpoints\nchflags uchg ~/.zcode/v2/checkpoints\n\n# Verify: this should fail with Operation not permitted\ntouch ~/.zcode/v2/checkpoints/test\n```\n\nOn Linux:\n\n```\nrm -rf ~/.zcode/v2/checkpoints\nmkdir -p ~/.zcode/v2/checkpoints\nsudo chattr +i ~/.zcode/v2/checkpoints\n\n# Verify: this should fail with Operation not permitted\ntouch ~/.zcode/v2/checkpoints/test\n```\n\nWhat 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.\n\nThe 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.\n\nThe pattern here is bigger than one app. Any tool that touches your source code deserves three questions:\n\nAudit 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.", "url": "https://wpnews.pro/news/an-ai-coding-app-was-silently-uploading-your-entire-git-history-inside-the-zcode", "canonical_source": "https://dev.to/jamilxt/an-ai-coding-app-was-silently-uploading-your-entire-git-history-inside-the-zcode-incident-15j", "published_at": "2026-09-20 16:24:41+00:00", "updated_at": "2026-09-20 16:54:29.532095+00:00", "lang": "en", "topics": ["ai-tools", "ai-products", "developer-tools", "ai-safety"], "entities": ["ZCode", "Zhipu", "ferstar", "zcode.z.ai", "Aliyun OSS", "Electron"], "alternates": {"html": "https://wpnews.pro/news/an-ai-coding-app-was-silently-uploading-your-entire-git-history-inside-the-zcode", "markdown": "https://wpnews.pro/news/an-ai-coding-app-was-silently-uploading-your-entire-git-history-inside-the-zcode.md", "text": "https://wpnews.pro/news/an-ai-coding-app-was-silently-uploading-your-entire-git-history-inside-the-zcode.txt", "jsonld": "https://wpnews.pro/news/an-ai-coding-app-was-silently-uploading-your-entire-git-history-inside-the-zcode.jsonld"}}