After the client encrypts and uploads your workspace, why you cannot open the local ciphertext

On 18 September 2026, developer ferstar was clearing disk space and found that a signed-in AI coding client packed the whole workspace into an encrypted archive, then tried to upload it straight to object storage. The archive was ciphertext. The RSA public key that wrapped the content key was issued by the server on the spot, and the matching private key never landed on the machine. A few hundred megabytes of “already encrypted” files sat on disk that neither you nor the client could open. On 21 September the vendor said it would open-source the client and remove the upload path. Those words—“encrypted upload”—did not become zero-knowledge because of that.

Start with the conclusion: “already encrypted” only means the packed bytes are not a plaintext workspace. It does not mean you hold the decryption key, and it does not mean the cloud cannot read the contents. In envelope encryption, a symmetric key is wrapped with a public key the server just issued, and the private key stays in the cloud. The design goal is that the cloud can open it. What you can check on this machine is three things: whether you can open this ciphertext yourself, whether Network shows workspace plaintext, and whether a new pending archive still appears after you turn the switches off. Do not read “AES ran locally” as “only I can decrypt.”

Answer the search intent in one sentence

Encryption on the path and “you hold the key” are not the same layer. A client can run AES on this machine, turn a workspace into ciphertext, and send that ciphertext to object storage. If the public key that wraps the content key comes from the server, and the private key never entered this computer, the file is not a backup you can restore. It is an envelope only the cloud can open. A path observer does not see plaintext. The server that holds the private key does.

This note is for people who just saw a headline about a client encrypting a workspace before upload, and who want to split “ciphertext is on disk” from zero-knowledge. It is also for developers who need to hand a repo or a key to a coworker without handing decryption rights to a vendor. It does not replace the vendor’s notice, and it is not an uninstall guide for one editor. Take away who holds the key in envelope encryption, how wide the plaintext manifest already was, what “destroy immediately” does not prove, and how to check on this machine whether you can open the file.

What the unpacking actually pinned down

On 18 September 2026, ferstar published the investigation. It did not start as a packet-capture contest. It started as disk cleanup. Local ~/.zcode was more than 700 MB. Under v2/checkpoints/ sat an .enc of about 313 MB. The status file next to it said the workspace was about 345 MB, kind was baseline, and failureCount was 564. The repo itself was about 10 GB. After dependencies were left out, the remaining 345 MB was almost all core project files.

He reconstructed the upload as two steps. First, the client called POST /api/v1/snapshot/upload-credential on zcode.z.ai. The server returned a snapshot id, an object-storage form signature, a size cap, and an RSA public key for that round. Second, the machine built a tar.gz, encrypted it with a one-time symmetric key under AES-256-CTR, wrapped that key with RSA-OAEP-SHA256, and POSTed tar.gz.enc as a form straight to Alibaba Cloud OSS. A storage callback registered the object with the vendor backend. The application server never touched the ciphertext file. Registration and the private key still sat in the cloud.

The scope was not the current working tree. A local plaintext manifest listed 42,411 files: .git/lfs/ about 196.1 MB (56.8%), .git/objects/ about 102.2 MB (29.6%), .git/logs/ about 0.6 MB, and source plus docs about 46.2 MB (13.4%). .git was 86.6% of the pack. A successful upload would have given the cloud commit history, secrets that were deleted from the tree but still lived in the object store, names of branches that were never pushed, and intranet hosts in .git/config. A second, cross-workspace manifest hashed global config and took that along too.

At 17:44 on 18 September the vendor posted in its user community. It said the upload came from a code-repo index that was on by default, used for session checkpoints, version rollback, and Repo Wiki, and that data was destroyed as soon as the Wiki was generated. On 21 September, National Business Daily reported that ZCode would open-source the client; the China Academy of Information and Communications Technology said the zcode-prod bucket held zero data; NSFOCUS said the bucket and objects had been deleted; and client v3.14.0 had removed snapshot generation and the upload path. ferstar added a split result: the 313 MB commercial project exceeded the size cap, retried 564 times, and never left the LAN. Another workspace, a public repo of 538 files, compressed and encrypted to about 15 KB, and the status was “accepted by the server.” So “did anything leave this machine?”—at least that small one did.

Why envelope encryption is not a file you can open

AES-256-CTR plus RSA-OAEP is the textbook envelope. Content is encrypted with a short-lived symmetric key. That key is then wrapped with a public key. The algorithm names look strong. Who can read the file depends on where the public key came from and where the private key sits. The write-up is explicit: the public key was issued when the client asked for an upload credential, the matching private key never entered this computer, and unwrapping the envelope with private keys already on the machine failed. The client itself could not open the ciphertext on disk.

That is the opposite of a backup you can restore. Time Machine, local Git objects, and a file you encrypt with a passphrase and then copy away all keep the decryption material on your side: a passphrase, a local key, or a keychain you can open. When the server alone holds the private key, the design goal changes. A third party on the path should not see the bytes. After arrival, the cloud should be able to open them. ferstar’s own line pins the purpose down: a key only the server can use serves the server reading your code. It does not serve rollback for you.

He Yuan, of the Data Law Research Center at Shanghai Jiao Tong University, told National Business Daily a narrower point. Encryption is a security measure. It is not anonymization, and it does not replace notice or data minimization. When the provider still holds the ability to decrypt, encrypted data can still be recovered and processed. “Delete immediately after use” only lowers long-term retention. It does not answer whether the first collection was necessary. Put the two sentences together: algorithm strength does not prove who holds the key, and a destruction statement does not prove the collection scope.

Do not use a production repo, a live cloud key, or customer code to “reproduce the upload.” Check whether a pending ciphertext is already on this machine, what path and failure count the status file records, and whether you can open the file yourself. Sending the live repo again does not put the private key back in your hands.

The plaintext manifest already states the scope

You cannot open the ciphertext. That does not mean the scope is a guess. The file list written at pack time stayed on disk, and it was plaintext. Those 42,411 rows split “the current source tree” from “the life of the repo.” The current tree was about 13.4%. The rest was the LFS cache, the full object store, and the reflog. An API key you already deleted from the working tree can still sit in .git/objects if it was ever committed. An unpushed branch name can expose a feature that is not public yet. An intranet Git hostname lives in .git/config. You do not need to open the ciphertext to see, from the manifest side, that it was packed.

Two switches on the screen look like master switches. In the code they were not. After the write-up lined them up, “improve the experience” only controlled whether data could be used for model training, and “repo snapshot index” only controlled whether the cloud built an index. Packing and the direct upload still ran while the login JWT was valid. Capture points sat before each prompt, and on repo-wiki-update when a task ended. One active session was logged at up to 62 captures. About half an hour after the pending archive was deleted by hand, a new 313 MB file appeared, and the failure count moved from 564 to 565. Deleting the file is not turning the switch off.

The privacy policy archived on 18 September still said it was last updated on 2026-06-15. The collection scope it described was text, files, and code submitted in a conversation. It did not say “after you sign in, automatically pack the full workspace and Git history.” The policy can support one claim: the vendor said it collected what you submitted in a session. It cannot support another: that a full snapshot was already written on the page you agreed to. Switches and the privacy policy both have to be checked against the artifact. Is there a new .enc locally? Does workspacePath in the status file point at the commercial project you are writing?

What “encrypted” and “destroyed immediately” can each prove

Split the sentences in the news into “this supports” and “this does not support.” That is steadier than following the adjectives. Ciphertext on disk supports only this: the packed bytes are not a plaintext directory tree. Immediate destruction supports only this: the vendor says it does not keep that object for long after the Wiki is generated. A search of the open-source tree that finds no /api/v1/snapshot/upload-credential supports only this: the path is gone from the tree that is public now. A bucket evaluated as holding zero data supports only this: that bucket was empty at the time of the evaluation.

None of those sentences covers the other side. Ciphertext does not support “you can restore the workspace.” Immediate destruction does not support “nobody held the private key before deletion,” and it does not support “the collection scope matched the option you checked.” An open-source tree flattened into two commits cannot be used to compare how the old sidecar was written. A bucket emptied after the fact cannot be rewound to show who decrypted or copied objects already accepted before 18 September. ferstar noted the tension: if checkpoint rollback really needed a full upload to the cloud, that fights “destroy immediately after generation” on the timeline. The checkpoint implementation in the open-source tree is a local Git diff. Metadata is JSON under ~/.zcode/checkpoints/. It does not depend on that upload path.

What you see What it can support What it cannot support
An .enc on disk, labeled AES-256 The packed bytes are not a plaintext workspace Only you can decrypt, and the cloud cannot read the contents
The RSA public key is issued when the client asks for a credential The private key that opens the envelope is not on this computer This is a backup for you to restore on another device
You turned off “improve the experience” and “repo index” Training consent or the cloud index may have stopped Packing and the direct upload stopped with them
The vendor says data is destroyed right after the Wiki is generated The vendor says it does not keep that object long-term The first pack matched the toggle you clicked
An evaluation says the bucket holds zero data That bucket was empty at evaluation time Objects accepted earlier were never decrypted or copied
The 313 MB file failed 564 times This commercial-project snapshot was not uploaded No workspace ever left any user’s machine

What you can check on this machine

A slogan is not evidence. The checks below cover only three layers you control: whether a pending artifact is already on this machine, whether a request carried workspace plaintext, and whether you can open this ciphertext yourself. Do not push another “test upload” into a production repo.

  1. Look at the local data directory before you look at the marketing page. In the checkpoint or cache folder of an AI coding client, a hundreds-of-megabytes .enc next to a status file that records workspacePath, encryptedSizeBytes, and failureCount means packing already happened. A path that matches the project you are writing is harder evidence than “we only send conversation context.”
  2. Open DevTools or a system network observer and turn on Preserve log. Read request lines and hostnames only: is there a call that asks for an upload credential, and is there a form POST straight to object storage? You are checking whether ciphertext left this machine, not sending the repo again. If the body contains source, a passphrase, or a .pem in the clear, this layer has already failed.
  3. Ask one question you can finish: can I open this local ciphertext with my own passphrase or a private key on this machine? If you cannot, and the vendor also says the client cannot, the key is not on your side. Only if you can open it is it fair to call the file a backup for yourself.
  4. Turn off every switch whose name sounds like privacy, index, or improvement. Wait one session cycle. Then look again: is there a new pending archive in the checkpoint directory, and did the failure count keep rising? The label on a switch does not prove a master cutoff. A new file does.
  5. When you need a file you can still open, switch to a channel whose compute boundary is written down. Open File Encryption Box, pick a disposable canary, and name it canary-fp-20260922-vendor-key.txt. Keep the passphrase on this computer. Open Network, turn on Preserve log, and encrypt. The request line and body should not contain the file contents, the passphrase, or the full filename as a business field. The output is .lock or .enc. One file is capped at 5 GB. Decrypt on this machine with the same passphrase. The canary should come back. You can open it because the passphrase is in your hands, not because the algorithm name contains AES.

Step five does not ask File Encryption Box to replace a coding client. It gives you a contrast. The same phrase, “encrypted on this machine,” can mean key derivation and AES-256-GCM stay in the current tab, and the file and passphrase are not uploaded as business data. You can carry the ciphertext, and you can open it. An earlier note covered a different layer: HTTPS only protects the hop to a router. The extra layer here is who opens the envelope after the ciphertext has left.

Do not send the full passphrase and the ciphertext on the same chat. A File Encryption Box .lock can be copied to a drive. The passphrase should travel on a channel you have already checked, or a short passphrase can sit after # on a burn-after-read link. Tape the key and the lock into the same message, and the envelope is already gone for anyone who can see that message.

Three ways to over-read “local encryption”

The first is reading “AES was called on this machine” as “only I can decrypt.” A local call only means the plaintext was still on this computer while it was packed. When the public key comes from the server and the private key never lands, decryption rights sit in the cloud. The algorithm list, the key length, and CTR versus GCM do not answer whose process holds the private key.

The second is reading “uploaded straight to object storage, not through the application server” as “the vendor cannot see it.” The file can skip the application server. The registration callback and the private key still sit on the vendor’s side. Skipping the application room reduces a plaintext copy in application logs. It does not move decryption rights. The router note showed that HTTPS does not stop an application-layer proxy. What this note does not stop is the end that holds the private key.

The third is reading “destroyed immediately / open-sourced / the bucket is empty” as “never collected.” Destruction lowers retention. Open source lets you check the tree that is public now. An empty bucket means there was no object at evaluation time. None of those automatically means historical objects were never opened, or that the switches at the time covered a full Git history. What belongs in a handoff note is whether this machine is still generating a pending archive, and whether you can open the ciphertext you are about to send. A headline that says the issue was fixed is not that note.

FAQ

If the file on disk is already ciphertext, is that local zero-knowledge?

No. Ciphertext only means the packed bytes are not a plaintext workspace. If the RSA public key that wraps the content key is issued by the server on the spot, and the matching private key never lands on the machine, neither you nor the client can open the file. That envelope is for the cloud to read, not a backup you can restore.

The vendor says uploads are destroyed immediately. What can you still check?

“Destroy immediately” lowers how long an object is kept. It does not answer whether the first pack went beyond the toggle you clicked, or who held the private key before deletion. An empty bucket after the fact cannot show who decrypted or copied objects already accepted before 18 September.

If I turn off “improve the experience” and “repo index,” does the upload stop too?

In the client that was unpacked, those two toggles controlled training use and whether the cloud built an index. Neither cut off packing or the direct upload. Look for a separate control that stops snapshot generation, and check whether a new pending ciphertext still appears locally after you turn the others off.

How do I hand a coworker a file I can still open?

Encrypt in the current tab with AES-256-GCM, and tell the passphrase only to a recipient you have already checked. Open File Encryption Box. One file is capped at 5 GB. The output is .lock or .enc. The file and passphrase are not uploaded by default. Open and use—no account. For a short secret, use Burn-Link and put the key after #. The link shape is s.html?id={id}#{key}. If a ticket or a log still carries a phone number or an ID number, run rule-based redaction in Privacy first. The source text is not uploaded as business data.

When you need to open it yourself, use a channel that is ready now

If you want a page that writes the compute boundary in the open, and you want to practice “I can open the ciphertext, and the passphrase does not enter Network,” open FastPwd File Encryption Box. No signup. Pick the canary above and run AES-256-GCM in the current tab, then follow the checks in the section above: the request should not contain the file contents or the passphrase, and the same passphrase should restore the file on this machine. You can copy the .lock or .enc to a drive. Do not write the passphrase into the same chat, and do not paste it into an AI box.

When you still have to hand a repo excerpt to a cloud model, assume the receiving end can see the full text you paste. A router leaves a plaintext call package where HTTPS ends. The earlier note already covered that. This note adds one question: if the client “encrypts” and then uploads, is the private key that opens the envelope on your side? If both layers fail, do not put a live cloud key, an SSH private key, or customer data into a workspace and then hand that workspace to a tool that packs it. Rule-based redaction can mask common fields. A whole key should go through Burn-Link, or it should not leave at all.

The next time a note says “we encrypt locally, then we upload,” remember three things. First, ask who holds the private key or the passphrase. Do not stop at the algorithm name. Second, ask whether the manifest covers .git history that is not the current files. Third, compare against a ciphertext you can open yourself, not against a destruction statement. Those three sentences are what you can write into a handoff note. “It is ciphertext on disk, so the cloud cannot see it” is not.

Further reading

A password on an archive before a cloud upload does not hide filenames, and it is not local AES-GCM. See What a ZIP password blocks before a cloud upload, and why it is not local AES-GCM. HTTPS only encrypts the hop to a router, and it does not stop an SSH key inside a prompt. See Why SSH keys show up in a 6TB LLM router dump, and which layer HTTPS cannot stop. To check a page that says “local encryption” against Network yourself, see How to check if browser encryption never uploaded plaintext.