~/satyajit

ZCode, open-sourced: what a remediation can and cannot establish

mdjsonmcp

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.

zai-org/ZCode@872ad96 · snapshot 2026-09-22
tracked files
6,973
license
Apache-2.0
branch
main
tests
4 files
source
34.5 MB
commit date
2026-09-20
source by language
TypeScript33.7 MB(3898)JavaScript582.1 kB(118)CSS103.6 kB(5)C#64.1 kB(1)HTML20.8 kB(6)Shell12.1 kB(7)Swift3.0 kB(1)

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

where a workspace snapshot goes, before and afterleft: reported · right: read from the published source
as reported, client 3.12.3removed in 3.14.0working treesource, plus the whole .git directoryarchive built in ~/.zcodepacked and encrypted client-sideupload credential requestedserver issues a key and limitsdirect upload to object storagethe bucket third parties later auditedtwo of four steps cross the machine boundarygit checkpoint, client 3.14.0packages/services/src/git/repo/gitCheckpointRepo.tsworking treescoped to the workspace pathspectemporary GIT_INDEX_FILEuser's real index untouchedwrite-tree · commit-treeobjects land in the repo you already haveupdate-ref, hidden refrestore, diff, delete — all localzero steps cross the machine boundary
stays on the machinecrosses to the vendor
The left column is not drawn from code — the code is not in the published repository. It is the shape described by the reporter and confirmed at the level of "this workflow existed and has been disabled" by both commissioned assessments. The right column is read directly from the source now on GitHub.

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

what a clone answers — zai-org/ZCode, main, 2026-09-22Apache-2.0 · 3,896 TypeScript files
cat package.json | jq -r .version3.14.0

The 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 matches

The removed feature leaves no trace in the tree — not a stub, not a flag, not a dead import.

grep -rn 'repoSnapshot|encryptedSizeBytes|captureBeforePrompt' src0 matches

None of the identifiers named in the public disclosure exist here.

grep -rn 'zcode-prod|PostObject|aliyuncs' --include='*.ts*' .0 matches

The only surviving Aliyun strings are four DashScope API endpoints in config/provider — a model provider, not storage.

sed -n '/## 二、上传接口/,/## 三、/p' NOTICE.md17 outbound scenarios

A 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 matches

The replacement checkpoint is a hidden git ref in the user's own repository.

Commands are shortened for width; paths are relative to the repository root. Every one of these is cheap to re-run, which is the whole value of open-sourcing a client — and every one of them describes the present tree, which is the whole limit of it.

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.

six published claims, four kinds of evidencenone of these is disputed here
checkable artefactZCode statement · both assessments

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.

point-in-time observationCAICT

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.

point-in-time observationNSFOCUS

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.

negative findingNSFOCUS

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

company statementZCode statement

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.

company statementZCode statement

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.

checkable artefact: you can verify it yourself, today, from published material · point-in-time observation: true of the system when it was looked at; says nothing about before · negative finding: nothing was found; the scope of the search bounds the claim · company statement: a claim about internal state, not independently checkable

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:

  1. 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.
  2. Two independent assessments, not one, with the second one's finding stated in the harder form — objects and bucket deleted, not merely empty.
  3. 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.
  4. 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

  1. 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 main across 3,896 TypeScript files, for repo.?wiki, repoSnapshot, encryptedSizeBytes, captureBeforePrompt, zcode-prod, PostObject and aliyuncs; 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.

  2. 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 log is two commits dated 20 and 21 September and there are no tags and no releases, so there is no earlier tree to diff against.

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

  4. The remediation kept the feature and moved it local, rather than deleting it.

    Read packages/services/src/git/repo/gitCheckpointRepo.ts and gitCheckpointStore.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.

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

Cite this article

For attribution, please use the following reference or BibTeX:

Satyajit Ghana, "ZCode, open-sourced: what a remediation can and cannot establish", ai.thesatyajit.com, September 2026.

bibtex
@misc{ghana2026zcodedisclosure,
  author = {Satyajit Ghana},
  title  = {ZCode, open-sourced: what a remediation can and cannot establish},
  url    = {https://ai.thesatyajit.com/articles/zcode-disclosure},
  year   = {2026}
}
share