Is ZCode safe? What the open-sourced code shows
Z.ai published ZCode’s source on September 21, 2026, three days after a developer writing as ferstar caught the client packaging whole workspaces, .git directory included, encrypting them and posting them to Alibaba Cloud object storage. The repository’s two commits are both timestamped September 20 UTC.
This is a source read, not a hands-on test. We never installed or ran the desktop app. Everything below comes from the published tree at commit 872ad96, version 3.14.0, the release both of Z.ai’s assessors cleared.
Short version: the upload pipeline really is gone from the published source and the endpoint really is dead, so ZCode is safer than it was a week ago. The parts you still can’t check are whether the binary you download matches the repository, and what happened to the archives uploaded before the fix. Along the way the client turned out to do two things worth knowing about that we hadn’t seen written up.
Key takeaways
- The upload pipeline is gone from the published source. At commit
872ad96there’s no snapshot upload route, notar.gz.encpacking and no Aliyun OSS path. ferstar reached the same conclusion in his own source review on September 21; we confirmed it independently, and the credential endpoint returned 404 both times we called it on September 22, at 16:58 and 20:18 UTC. - Z.ai’s public changelog records the whole episode as one bug-fix line. Under v3.14.0, released September 19: “Fixed an issue with abnormal uploads in the repository wiki.” Nothing about the incident, the deleted bucket, the audits or the open-sourcing appears anywhere in the release notes.
- The 313 MB archive everyone quoted never actually uploaded. It failed 564 times on a size limit and sat in
pending. A much smaller workspace did go through: 538 files, about 15 KB encrypted. - The client silently reroutes two model endpoints, both Z.ai’s own, to
zcode.z.ai, authorization header included. It’s plan-entitlement routing rather than key harvesting, but it happens per request and you won’t see it. - The CLI defaults to
yolowhen you run it non-interactively, and in that mode an ordinary tool is cleared at line 136 of the permission service, before thedisallowedToolsdeny at line 149 ever runs.
What ferstar found, and what Z.ai did
The numbers in the original research are his, not ours, and one of them is routinely misquoted. ferstar’s writeup reconstructs the pipeline from the packaged Electron bundle: the client requests upload credentials from zcode.z.ai, receives OSS form signatures and a per-round RSA public key, packs the workspace, encrypts it and posts the archive straight to Aliyun OSS. The private key stays in Z.ai’s cloud, so the ciphertext on your own disk is unreadable to you and to the client that made it.
In his capture a 345 MB commercial workspace became a 313 MB archive, 86.6% of it the .git directory. That one never uploaded. It failed 564 times against a size limit, sat in the local pending directory, and he confirmed at his own router that the chunks never left the network. A separate, much smaller workspace did go through: 538 files, roughly 15 KB once compressed and encrypted, accepted by the server. So code did leave machines. It wasn’t the archive most of the coverage quoted, and he went back and said so.
Z.ai attributed the behaviour to codebase indexing used for local indexes, session checkpoint restore and Repo Wiki. Then it shipped 3.14.0, deleted the bucket, commissioned assessments from CAICT and NSFOCUS, and published the client.
ferstar got to that source first. On September 21 he updated his post with a review of the published repository, confirming the flattened history, the purged upload pipeline and a 27 KB NOTICE.md, and he took apart the company’s own explanation with it: the checkpoint implementation in gitCheckpointService.ts runs on the local git CLI and writes JSON under ~/.zcode/checkpoints/, with no cloud dependency at all. Checkpoint restore never needed a full-repo upload.
So we went looking at what he didn’t cover: where the client still sends your model traffic, what the CLI does to your permission rules when you script it, and what the changelog says about any of this.
Is the upload code actually gone?
Yes, as far as the published tree goes, and we’d rather confirm that plainly than manufacture a gotcha.
We cloned zai-org/ZCode at 872ad96 and searched for every piece of the pipeline. There’s no route matching snapshot/upload, no tar.gz.enc, and no workspaceSnapshot or repoSnapshot symbol anywhere. Alibaba Cloud does still appear, and not as a destination: the remaining hits are DashScope entries in the bundled provider catalogue, where Alibaba’s model service is one more endpoint you can configure, plus a logo URL and a string check against an error host. We sent a bare POST to https://zcode.z.ai/api/v1/snapshot/upload-credential on September 22, at 16:58 UTC and again at 20:18 UTC. Both returned 404, in 0.74 and 0.59 seconds. A GET to /en/changelog on the same host at the same moments returned 200, so the host is up and answering.
One leftover is worth knowing about, and it’s smaller than it looks. packages/desktop/src/main/networkTelemetryAggregator.ts carries a list of known URL path segments, used to bucket request latencies and errors by endpoint. snapshot sits on line 113 of that list, between sessions and status; upload-credential sits on line 119, between token and usage. That isn’t upload code and we’re not going to call it upload code. It’s vocabulary: the telemetry layer still knows the names of endpoints the client no longer calls.
The fix appears in the changelog as one line
The entire episode gets a single entry in ZCode’s public changelog. The removal, the deleted bucket, the two assessments, the open-sourcing — all of it is one item in the bug-fix list for v3.14.0, released September 19:
Fixed an issue with abnormal uploads in the repository wiki

That’s the whole disclosure on the page a user would actually check. The same release note carries twelve new features above it, including a three-step onboarding guide and an invitation rewards centre. Scroll the rest of the history, back to 3.10.1 in late August, and there’s no mention of the incident, the audits or the repository going public.
We nearly got this wrong ourselves. A summary of that page put the line under 3.12.3, which shipped September 17 — the day before ferstar published, and two days before the fix went out. The page itself puts it under 3.14.0. If you’re checking this after us, read the changelog rather than a description of it.
Worth noting if you’re looking today: 3.14.0 is the version in the repository, but the changelog already lists 3.14.1 and 3.14.3, both dated September 22. The open-sourced snapshot and the build you’d download are two releases apart.
You can’t diff the fix
ZCode’s repository has two commits: an empty initial commit and feat: open source, both timestamped September 20 UTC, with the repository made public on the 21st. Apache-2.0, and around 6.3k stars when we looked on September 22.
That matters more than it sounds. The most useful thing an open-sourced client could offer right now is the ability to watch the upload path being removed, and to see what else moved in the same release. Neither is available. Issues are closed and pull requests are locked, so it’s a one-way drop rather than a project you can file against. You can read what ZCode does today. You can audit the next drop against this one. That’s real, and it’s narrower than “the code is public now” suggests.
It also leaves the gap the assessors can’t close for you: the published source describes the client, not the build on the download page. Those are usually the same thing. Usually isn’t a guarantee, and reproducible builds are how you’d turn that into one. The two releases that shipped since make the distance concrete.
The disclosure document they didn’t translate
NOTICE.md is 27 KB and it only exists in Chinese. ZCode’s README has an English twin, README.en.md. The document enumerating what the client sends does not.
It’s a thorough piece of work. It walks through model and auxiliary requests, login and credential flows, plan and payment calls and provider directory checks, and it links individual claims to specific source files and line numbers. Anyone writing about what this client sends should be reading it. Almost nobody in English has.
The item that caught our eye is the model gateway. Z.ai documents it — in NOTICE.md, which is the half of the disclosure that never got translated. We haven’t seen it discussed in English. In official-coding-plan-gateway.ts the client wraps the provider’s fetch and compares each request URL against a two-entry map:
// abbreviated: type annotation removed
export const OFFICIAL_CODING_PLAN_GATEWAY_ROUTES = [
{ providerEndpoint: "https://open.bigmodel.cn/api/anthropic/v1/messages",
gatewayPath: "/api/v1/ultra/anthropic/v1/messages" },
{ providerEndpoint: "https://api.z.ai/api/anthropic/v1/messages",
gatewayPath: "/api/v1/ultra-zai/anthropic/v1/messages" },
];
A match is re-sent to zcode.z.ai with the method, body, query string and authorization header preserved, and the Host header dropped so fetch recalculates it. Matching is on protocol, host, port and path together, normalised for case and trailing slashes, so a provider you configured yourself doesn’t hit the map at all.
Both endpoints belong to Z.ai. This is plan-entitlement routing for their own subscription, not a client harvesting third-party API keys, and we’d be inventing a scandal if we framed it otherwise. What’s worth knowing is that it happens silently and per request. Configure the Anthropic-compatible endpoint https://api.z.ai/api/anthropic/v1/messages and that traffic, credentials included, goes to a host you didn’t configure. Any other path on the same host, the OpenAI-compatible route included, misses the map and goes where you pointed it. ZCODE_BASE_URL and ZCODE_ENDPOINT_ORIGIN move the target.
The default we’d change before putting it in CI
ZCode’s CLI runs headless prompts with permission prompts off. run.ts line 42 sets it:
const DEFAULT_HEADLESS_PROMPT_MODE: CliPermissionMode = "yolo";
Run zcode --prompt "…" without --mode and you get yolo. In permission/service.ts that returns an allow with the reason string “Yolo mode bypasses permission prompts”.

The ordering is the part to notice, and it took us two reads to state correctly. The yolo allow sits at line 136; the general disallowedTools deny sits at line 149. For an ordinary tool, yolo wins and your deny list is never consulted.
Three things escape that. A tool flagged requiresUserInteraction gets its own disallowedTools check at line 113 and returns before yolo is evaluated. A tool declaring alwaysAsk is routed out at line 131. And the yolo branch is guarded by !planEnabled, so in plan mode the deny list works normally. A source comment acknowledges the precedence directly, so this is known behaviour rather than a bug we’re announcing.
If you’ve used Claude Code’s auto mode, the mental model is different: there a classifier decides what to wave through. Here it’s a flat bypass. And auto isn’t an escape hatch either — service.ts has a branch for it marked reserved and not implemented, but the CLI’s mode parser rejects the value first with Supported modes: build, edit, plan, yolo. Pass --mode explicitly in anything scripted.
So, is ZCode safe?
Safer than it was a week ago, and still asking you to trust things you can’t check.
What the source supports: the pipeline is gone, the endpoint is dead, and the company moved faster and more concretely than most would. What the source can’t support: whether the archives uploaded before September 19 were deleted, whether the binary matches the repository, or whether the code was ever used for training. Those rest on Z.ai’s statement and on two assessments Z.ai commissioned, whose full report was still unpublished when we checked.
For a hobby project on code that’s already public, the calculus is easy. For anything proprietary, we’d wait for the assessment report, and in the meantime this is the same question we’ve worked through for OpenCode’s source, for what Copilot puts on the wire, and across the rest of our guides: not whether a vendor is trustworthy, but how much of their claim you can verify without asking them. With ZCode that number just went up. It didn’t go all the way.
One thing we couldn’t settle, and it nags. The NSFOCUS summary, as relayed from Z.ai’s statement, reports no outbound transmission paths detected. Read strictly, that’s about the snapshot pipeline and it matches what we found. Read loosely, it’s a strange thing to say about a client that ships a shell tool, a file tool and an embedded browser, any of which will move a file off your machine when the agent decides to. The difference between those two readings is the whole question, and it sits in a summary rather than the report. More agent security work than we’d like ends up parked exactly there.
Frequently asked questions
Did Z.ai actually remove the git history upload?
From the published source, yes. Searching the tree at commit 872ad96 (version 3.14.0) for the pipeline turns up no snapshot upload route, no tar.gz.enc packing and no Aliyun OSS upload path, which matches what ferstar reported in his own source review on September 21. We also called the credential endpoint the original writeup named, POST https://zcode.z.ai/api/v1/snapshot/upload-credential, twice on September 22, 2026, at 16:58 and 20:18 UTC, and got a 404 each time while the same host served its changelog page with a 200. What nobody outside Z.ai can tell you is what happened to the archives uploaded before the fix, or whether the code that's published matches the binary you download.
Was a 313 MB commercial repository really uploaded?
No, and the researcher who found the incident corrected this himself. The 313 MB archive failed 564 times against a size limit and stayed in the local pending directory; ferstar checked connection tracking on his own router and confirmed those chunks never left his network. What did leave was a much smaller workspace: 538 files, roughly 15 KB once compressed and encrypted, accepted by the server. So data did go out, just not the archive most coverage quoted.
Does ZCode reroute requests to your own model provider?
Only two exact URLs, both Z.ai's own. In apps/zcode-cli/packages/adapters/src/model/official-coding-plan-gateway.ts the client matches on protocol, host, port and path together, and the map contains open.bigmodel.cn/api/anthropic/v1/messages and api.z.ai/api/anthropic/v1/messages. A match is re-sent to the ZCode gateway with the method, body, query string and authorization header preserved. Any other path, including Z.ai's own OpenAI-compatible route, misses the map and goes where you pointed it. Providers you configure yourself are never touched.
Is it safe to run ZCode in CI?
Not on the defaults, in our reading. In apps/zcode-cli/packages/cli/src/run.ts the headless prompt mode constant is 'yolo', so zcode --prompt without an explicit --mode runs with permission prompts bypassed. In the permission service the yolo allow at line 136 is evaluated before the general disallowedTools deny at line 149, so for an ordinary tool your deny list is never consulted. Three things still stop it: a tool flagged requiresUserInteraction hits its own disallowedTools check at line 113, a tool declaring alwaysAsk is routed out at line 131, and plan mode disables the yolo branch entirely. If you script it, pass --mode explicitly.