2026-09-22 · 12 min · security · disclosure · agents · developer-tools · open-source · privacy
On 21 September 2026, Z.ai published the source of its AI coding workbench ZCode at
github.com/zai-org/ZCode under Apache-2.0, alongside a
public statement about security issues reported by the community. The statement says
remediation completed in client v3.14.0, that the Repo Wiki feature was removed and the
local-repository-snapshot generation and upload workflow disabled, and that two third parties
were engaged to assess the result. It apologises, thanks the reporters, and commits to a
vulnerability reporting process with rewards.
I want to be careful about what this article is. It is not an attack and it is not a summary of the press release. A client that builds and uploads snapshots of a developer's working tree is a specific and serious class of bug — the working tree of a professional repository is source, unpushed branches, and whatever has ever been committed to it — and the shape of the response here is notably complete: fix it, remove the feature, open the client, commission independent review, publish. Most responses stop at the first two. That deserves saying plainly.
It also deserves reading precisely, because the published evidence is of four different kinds and only one of them is the kind you can check yourself.
- license
- Apache-2.0
- branch
- main
- tests
- 4 files
- source
- 34.5 MB
- commit date
- 2026-09-20
by size of tracked source at this commit, file counts in brackets; docs, data and vendored trees excluded
local clone, 2026-09-22 at 872ad96 — branch, commit, commitDate, fileCount, hasTests, languages, license, licenseFile, testFileCount
The feature, at the level it matters
The left-hand column is the reported pipeline. I did not read it in code, because it is not in
the published repository — that is the first thing the repository tells you. It is the shape
described by the researcher who reported it and corroborated, at the level of "this workflow
existed and has been disabled", by both commissioned assessments. A snapshot of the workspace
was built and encrypted on the machine, an upload credential was obtained from the service, and
the archive went directly to the vendor's object storage. The reported detail that makes it a
serious bug rather than a telemetry question is scope: the archive was of the workspace
directory, which on a real project means the .git directory comes along, and .git is every
version of every file the repository has ever held.
The right-hand column is what v3.14.0 does instead, and this one is in the repository, at
packages/services/src/git/repo/gitCheckpointRepo.ts. The replacement is a local git
checkpoint: a temporary GIT_INDEX_FILE, git add -A -- <workspace pathspec>, write-tree,
commit-tree, and update-ref into a hidden ref. The comment above it states the constraints
directly, and the third one is the incident, written as a rule:
关键约束:
- 不污染用户真实 index
- 不产生用户可见分支或普通 commit
- scope 永远跟随当前 workspace,而不是整个 repo 无差别快照Key constraints: do not pollute the user's real index; do not produce a user-visible branch or ordinary commit; scope always follows the current workspace, rather than an indiscriminate snapshot of the entire repo.
The engineering point worth taking away is that the feature survived. Checkpoint-and-restore in an agent that edits your files is a good feature — arguably a necessary one. The fix was not to delete it but to notice that git already provides content-addressed snapshot storage, locally, in a repository the user already has, and that the only thing the archive-and-upload design bought was a server-side index. Reusing the object store you are already standing on is both cheaper and has no boundary to cross.
What the repository can answer
cat package.json | jq -r .version3.14.0The published tree is the remediated client, not a pre-incident snapshot.
git log --oneline | wc -l2"Initial commit" (20 Sep) and "feat: open source" (21 Sep). There is no development history to read.
grep -rniE 'repo.?wiki' --include='*.ts*' --include='*.md' .0 matchesThe removed feature leaves no trace in the tree — not a stub, not a flag, not a dead import.
grep -rn 'repoSnapshot|encryptedSizeBytes|captureBeforePrompt' src0 matchesNone of the identifiers named in the public disclosure exist here.
grep -rn 'zcode-prod|PostObject|aliyuncs' --include='*.ts*' .0 matchesThe only surviving Aliyun strings are four DashScope API endpoints in config/provider — a model provider, not storage.
sed -n '/## 二、上传接口/,/## 三、/p' NOTICE.md17 outbound scenariosA vendor-written enumeration of every path that leaves the machine. A workspace-snapshot upload is not one of the seventeen.
grep -n 'update-ref|commit-tree' services/src/git/repo/gitCheckpointRepo.ts4 matchesThe replacement checkpoint is a hidden git ref in the user's own repository.
The greps come back empty, which is what the announcement says they should. The rows that actually carry information are the other three.
package.json reads 3.14.0, so the tree is the remediated client. git log has two
commits — "Initial commit" on 20 September and "feat: open source" on 21 September. There
is no development history. That is a completely ordinary way to open-source an internal
codebase, and it decides what this repository is for: it establishes what the client does now,
and it cannot be used to audit the removed feature, to date its introduction, or to see what
else changed alongside it. Anyone hoping to read the diff will not find one.
The strongest artefact in the repository is not code. NOTICE.md is a 27 KB document, written
by the vendor, enumerating in four sections what the client can execute, what leaves the
machine, what it stores locally, and its third-party licences. Section two is a table of
seventeen outbound-request scenarios — model calls, auth, billing, the Coding Plan gateway,
provider config, web search, MCP, SSH and container sync, attachments, conversation sharing,
idle-task tickets, feedback, updates, tool delivery, dev tooling, build and signing — each with
its trigger condition and data scope. A workspace-snapshot upload is not among them. An omission
from a document whose entire purpose is exhaustive self-description is a meaningfully stronger
signal than an empty grep, and publishing that document at all is the least-discussed and
possibly best part of this release.
The document is also unusually blunt about the general problem, in a way most agent vendors are not:
当前共享 Agent 执行适配器不提供默认的操作系统沙箱
The current shared agent execution adapter does not provide a default operating-system sandbox.
模型声称"已完成""已安全处理"或"已获授权"不构成事实或授权证明
A model claiming it has "finished", "handled this safely" or "been authorised" does not constitute evidence of fact or of authorisation.
What the assessments establish, and what they do not
Two organisations were engaged. Per the statement: the China Academy of Information and
Communications Technology (CAICT) confirmed the zcode-prod Alibaba Cloud OSS bucket is in
a zero-data state, and that v3.14.0 removed the Repo Wiki feature and cut the local-snapshot
generation and upload path. NSFOCUS confirmed that all data objects and the bucket itself
have been deleted, and that no functional path capable of triggering local repository snapshot
generation or transmitting local files externally was identified.
Both are real findings and both are worth having. They are also two different kinds of statement, and the company's own claims are a third and fourth kind.
Remediation is complete in client v3.14.0; the Repo Wiki feature is removed and the local-snapshot generation and upload workflow disabled.
The published tree is v3.14.0 and contains no such path. Verifiable in a clone.
The zcode-prod object-storage bucket is in a zero-data state.
True of a bucket that was full yesterday and emptied this morning. A zero-data state is a fact about now.
All data objects and the bucket itself have been deleted.
Deletion is the strongest form of this claim and still describes only that bucket, at that moment.
No functional path capable of triggering local snapshot generation or transmitting local files externally was identified.
A bounded review of a stated scope at a stated time. 'Not identified' is the honest phrasing and it is not 'does not exist'.
The referenced code data is not retained.
A statement about what the company holds. No external party can confirm an absence inside someone else's systems.
The data was never used for model training.
A statement about a process that leaves no external trace. It can be believed; it cannot be checked.
Three things follow from that table, none of them accusations.
A zero-data bucket is a fact about the present. "The bucket is empty" and "the bucket has always been empty" are different sentences, and only the first one is checkable by looking at a bucket. Deletion — NSFOCUS's stronger finding — is better, because it removes the object rather than merely finding none, but it still describes one named bucket at one moment. Neither assessment claims otherwise; the phrasing in both summaries is careful. It is the reader who has to not round it up.
"No functional path identified" is a negative finding from a bounded review. It is the correct and honest way to report a review that found nothing, and it is exactly as strong as the scope of the review — which is not published in either summary. A point-in-time assessment of a stated scope is evidence; it is not a proof of absence, and no security assessment in the world produces one. The statement's own promise that full reports will follow is the thing that would change this, because a scope section is what turns "not identified" into a bounded, useful claim.
Two of the six claims are about internal state. That the referenced code data is not retained, and that it was never used for model training, are statements about what a company holds and what it did with it. No external party can confirm an absence inside someone else's systems. That is not a criticism — it is the structural limit of every such statement by every vendor — but it means those two claims rest on the company's word, and are the two most load- bearing claims for an affected user. Both assessments are scoped to the bucket and the client, which is what a scoped assessment means, and is why the scope sections matter.
Credit where it belongs
The reporting worked. The issue was found by a user looking at disk usage, written up in public with specifics, and the vendor's statement came the same day; the open-sourcing and the two assessments followed within three days. That is fast by the standards of this industry.
Four things here are better than the norm and should be named, because the norm is what gets set by whether anyone notices:
- Open-sourcing the client rather than describing it. Every claim about what the client
does is now falsifiable by anyone with
git clone, permanently, for every future version too. That is a larger commitment than a one-off audit and a harder one to walk back. - Two independent assessments, not one, with the second one's finding stated in the harder form — objects and bucket deleted, not merely empty.
- Publishing
NOTICE.md. A seventeen-row table of everything that leaves the machine, with triggers and data scope, written before anyone asked for it in that form. I would like this from every agent vendor and I have it from approximately none of them. - Committing to a vulnerability reporting process with rewards. The reporter here had no channel and used a blog. A channel is the difference between this timeline and one where the finder sells it.
What is still outstanding is short and specific: the full assessment reports with their scope
sections, rather than summaries; and a statement addressed to users of the affected versions
about what, if anything, they should do — rotate what, check what. The statement covers the
bucket and the client. It does not cover the user's next action, and for anyone whose .git
history contains a credential that was once committed and later removed, that is the question
they actually have.
The general lesson, which is not about this vendor
Every agent that edits your files needs a way to undo that. Every implementation of undo needs somewhere to put the previous state. The design decision is whether that somewhere is on your machine or on someone else's, and it is a decision that gets made early, by one engineer, for reasons that are usually about product surface — a server-side index enables a repo-wide wiki, a cross-device history, a search box — rather than about data.
The v3.14.0 checkpoint is the proof that the local answer was available the whole time. git write-tree against a temporary index is content-addressed, deduplicated, instant on a warm
index, and has no boundary to cross. If you are building this, that is the default, and the
burden of proof belongs on the design that leaves the machine.
What would change my mind
5 claims above, and what would falsify each
The published repository is the remediated v3.14.0 tree and contains no snapshot-upload path.
A grep of a fresh clone finding one. Mine was run on 2026-09-22 against the tip of
mainacross 3,896 TypeScript files, forrepo.?wiki,repoSnapshot,encryptedSizeBytes,captureBeforePrompt,zcode-prod,PostObjectandaliyuncs; all returned zero outside four DashScope provider-config entries. A path under a different name would not be caught by a grep for the old names, and I did not audit the tree line by line — nobody has.The repository cannot be used to audit the removed feature.
A force-push or a second repository publishing the pre-remediation history. As of writing,
git logis two commits dated 20 and 21 September and there are no tags and no releases, so there is no earlier tree to diff against.A zero-data bucket says nothing about what was in it before.
Access logs, object-versioning records, or a retention-policy audit covering the period before remediation — any of which would turn a point-in-time observation into a historical one. If the full assessment reports contain that, this claim is too strong and I would withdraw it.
The remediation kept the feature and moved it local, rather than deleting it.
Read
packages/services/src/git/repo/gitCheckpointRepo.tsandgitCheckpointStore.ts. Checkpoint creation, diffing, restore and delete are all present and all operate on a hidden ref in the user's own repository. If a later version reintroduces a remote index for checkpoints, that changes.The response is more complete than the industry norm.
A list of comparable incidents where a vendor open-sourced the affected client, commissioned two assessments and published a full outbound-request enumeration. I can think of few; my sample is what I have read, not a survey, and "notably complete" is a judgement rather than a measurement. It is also a judgement about the response and not about the bug.
No figure, on purpose. A disclosure piece is the one place where a screenshot
is a liability: there is nothing here showing the reported pipeline in flight,
no captured working tree, no archive, no bucket listing, no endpoint, no request
or credential, and no reconstruction of any of them — anything of that kind
would be a recipe rather than evidence, however clearly it was labelled. What
the vendor published is text: a two-commit repository, a 27 KB NOTICE.md, a
statement and two assessment summaries. There is nothing to mirror either —
every one of the 2,370 image files in the tree is an application icon, a
file-type glyph from the Material icon set, installer artwork or the wordmark;
there is no architecture diagram, no screenshot and no recording anywhere in
it. So the three diagrams above are drawn from
the remediated code in
github.com/zai-org/ZCode, read on
2026-09-22 at the tip of main, and every path and identifier in them is one
you can git clone and check.