プログラミング
ZCodeの内部:Git履歴をサイレントにクラウドへアップロード
Inside ZCode: Silently uploading your Git history to the cloud (blog.ferstar.org)
要約
ZCode(ZhipuのAIコーディングデスクトップアプリ)が、ユーザーのワークスペース全体(Git履歴、LFSキャッシュなどを含む)を暗号化し、Aliyun OSSにサイレントにアップロードしていることが調査により明らかになりました。暗号化に使用される秘密鍵はサーバー側のみが保持しており、ユーザーは自身のデータを復号できません。UI設定ではこの機能を無効化できず、著者はファイルシステムレベルでの対策を提案しています。
全文翻訳
目次
私はネイティブな英語話者ではありません。この記事はAIによって翻訳されました。
ディスク容量を空けるためのルーチンチェック中に、興味深い発見がありました。~/.zcodeが700MB以上を占めていたのです。断続的に調査した結果、非常に驚くべき事実を確認しました。ZCode(Zhipuの公式AIコーディングデスクトップアプリ)は、ログインしている間、常にユーザーのワークスペース全体(完全な.git履歴、LFSアセットキャッシュ、reflogs、グローバルアプリ設定を含む)をパッケージ化し、暗号化し、直接Aliyun OSSにアップロードしていました。さらに皮肉なのは、暗号化に使用されるRSA公開鍵はサーバーからオンザフライで提供されるのに対し、秘密鍵はクラウド上にのみ存在することです。自分のディスク上にある数百MBの暗号文を、あなた自身もZCodeクライアントも復号することはできません。以下に、調査の完全な記録、証拠の連鎖、そしてそれを永久にシャットダウンするワンライナーの防御策を示します。
出発点:保留中の313MBアーカイブ
~/.zcodeはZCodeのデータルートです。サイズの内訳はおおよそ以下のようでした。
cli/: 約257MB(セッションデータベース、実行ログ)
computer-use/: 約130MB(バンドルされたアプリとランタイム依存関係)
v2/checkpoints/: 約303MB(主な容疑者)
v2/checkpoints/内で、状態メタファイルと共に313MBの.encファイルを見つけました。
{
"workspacePath": "/Users/ferstar/myprojects/<a commercial project>",
"lastCompressedSize": {
"encryptedSizeBytes": 313070842,
"workspaceSizeBytes": 345549173
},
"kind": "baseline",
"failureCount": 564
}
物語は単純でした。クライアントはアクティブな商用プロジェクトをスキャンし、node_modulesなどを除外し、残りの345MBをベースライン(フルスナップショット)としてラベル付けされた313MBの暗号化アーカイブにパッケージ化しました。564回のアップロード試行失敗を記録し、次のリトライを待ってローカルのpending/ディレクトリに置かれていました。リポジトリは合計10GBでしたが、依存関係を除くと、残りの345MBはほぼ完全にコアな知的財産でした。
どこへ行くのか:ログからasarへリバースエンジニアリング
ログには明示的なアップロードURLが含まれていなかったので、クライアントのapp.asarを解析しました。再構築されたアップロードフローは以下の通りです。
シーケンス図
参加者 C as ZCodeクライアント
参加者 S as zcode.z.ai
参加者 O as Aliyun OSS
C->>S: POST /api/v1/snapshot/upload-credential
S-->>C: snapshot_id + RSA公開鍵 + max_size + OSSフォーム認証情報 + コールバック
C->>C: tar.gzパック → AES-256-CTR暗号化 → RSA-OAEPラップキー
C->>O: tar.gz.encのPostObject直接アップロード
O->>S: コールバックが受信を確認
パイプラインは2段階で実行されます。
認証情報をコーディネーターにリクエスト:クライアントはhttps://zcode.z.ai(コード内のVITE_ZCODE_ENDPOINT_ORIGIN)を呼び出します。サーバーはOSSフォーム署名(ポリシー、x-oss-signature)、動的なObject Key、サイズ制限、そしてこの暗号化ラウンド用のRSA公開鍵を返します。
OSSへの直接フォームPOST:ローカルでアーカイブとストリーミング暗号化を行った後、クライアントはZCode自身のアプリケーションサーバーをバイパスし、tar.gz.encをHTTP POSTフォーム経由で直接Aliyun OSSに投稿します。OSSはその後、Zhipuのバックエンドにコールバックしてスナップショットを登録します。
アクティブなソケットを検査したところ、これが確認されました。実行中のZCodeプロセスは、zcode.z.aiのIPエンドポイントと2つのAliyun OSSストレージノードへの永続的なHTTPS接続を維持していました。
最も皮肉な部分:鍵はサーバーに属する
暗号化実装は、教科書的なエンベロープ暗号化を使用しています。
keyId: String(i.encryption.key_version),
keyWrapAlgorithm: "rsa-oaep-sha256",
publicKeySpkiPem: Ylt(i.encryption.public_key)
コンテンツは、AES-256-CTRを使用したエフェメラルな対称鍵で暗号化されます。対称鍵は、サーバーから提供された公開鍵を使用してRSA-OAEP-SHA256でラップされます。重要なのはその公開鍵です。これは認証情報ネゴシエーション中にサーバーから提供され、対応する秘密鍵はあなたのマシンに触れることはありません。私のシステム上のすべてのローカル秘密鍵でエンベロープ鍵をアンラップしようとしましたが、予想通り失敗しました。言い換えれば、あなたのドライブ上にある313MBの暗号文は、あなたやクライアントが開くことはできません。Zhipuのバックエンドだけがそれを解読する鍵を持っています。もしこの機能が本当にユーザー向けのロールバックやクロスデバイス同期のために構築されていたなら、鍵はローカルに(GitやTime Machineのように)存在するはずです。サーバーだけが使用できる鍵は、サーバーがいつでもあなたのコードを読めるようにするという、ただ一つの目的を果たします。
何がパッケージ化されるのか:約90%が.git
暗号文はロックされていますが、パッケージ化中に生成されるマニフェスト(ファイルインベントリ)はプレーンテキストでローカルに保存されます。42,411ファイルの1つのスナップショットの内訳:
コンテンツサイズ | 比率 | 含まれる情報
-----------------|------|------------------------------------------------------------------
.git/lfs/ | 196.1 MB | 56.8% | LFSキャッシュ — ダウンロードされたすべてのバイナリアセットと大きなメディア
.git/objects/ | 102.2 MB | 29.6% | 完全なコミット履歴オブジェクトストア(コミット、ツリー、ブロブ)
.git/logs/ | 0.6 MB | 0.2% | reflogs — ローカルブランチ履歴とプッシュされていない操作トレース
ソースコード & ドキュメント | ~46.2 MB | 13.4% | src/, 設定ファイル、内部ドキュメント
.gitディレクトリだけでペイロードの86.6%を占めています。アップロードされると、クラウドは現在のワーキングツリー以上のものを受け取ります。それはリポジトリの最初からのすべての系統を受け取ります。
* 後続のコミットで削除された過去のAPIキーと機密設定。
* 未プッシュのローカルブランチ名(リリースされていない機能計画を明らかにします)。
* .git/configに設定された内部GitLabホスト名とリポジトリパス。
さらに、repo_snapshot_extra_manifestという追加のマニフェストが、グローバルZCode設定ファイル(settings.behavior.jsonなど)のハッシュを生成し、スナップショットごとにワークスペース間でバンドルします。
スイッチの真実:UIトグルはそれを止めない
自然な反応は、設定を確認してオフにすることです。UIオプションとコードベースを相互参照しました。
スイッチ | あなたが期待すること | それが実際に行うこと
---------------------------------|---------------------------------|--------------------------------------------------------------------------------------
Optimize Experience (optimizeAgentExperienceEnabled) | テレメトリ/データ収集を無効にする | モデルトレーニングのためにデータが承認されるかどうかのみを制御します。スナップショットキャプチャとアップロードは引き続き実行されます。
Repo Snapshot Indexing (repoSnapshotIndexingEnabled) | スナップショット機能を無効にする | アップロードされたスナップショットをサーバーがインデックス付けするかどうかのみを制御します。ローカルパッケージングとアップロードは中断なく続行されます。
ホストアセンブリコードを見ると、キャプチャ/アップロードサイドカーが無条件で起動時にインスタンス化されていることが非常に明確になります。ユーザー設定に対するゲートチェックはなく、唯一の要件はtokenProviderが有効なJWTを返すことができることです。結論として、あなたがログインしている限り、このバックグラウンドパイプラインは常にアクティブであり、UI設定でそれをオフにすることはできません。キャプチャトリガーは2つのポイントで発生します:captureBeforePrompt(各プロンプトの前)とrepo-wiki-updateタグが付いたタスク完了時です。セッションログでは、単一のアクティブセッションが最大62回のキャプチャイベントを生成しました。
プライバシーポリシーは何と言っているか
ZCodeのプライバシーポリシーを確認すると、「会話中に送信されたテキスト、ファイル、コード」を収集すると明記されています。これはLLMにコンテキストを供給するための標準的なプラクティスです。しかし、ポリシー全体、FAQ、変更履歴を通して、ワークスペース全体と完全なGit履歴をサイレントにパッケージ化してアップロードすることについての言及は一切ありません。最も近い言及は、一般的なテンプレートステートメントです。「最適化プログラムはデフォルトでオフになっており、同意なしに入力はトレーニングに使用されません」。
防御策:削除はいたちごっこ;ディレクトリをロックする
最初に保留中のパッケージを見つけたとき、私はそれを削除しました。30分以内に、それは再キャプチャされました。新しい313MBアーカイブが作成され、リトライカウンターが564から565に増加しました。アップローダーはファイルがなくなったことを見ると、新しいものをパッケージ化するだけです。手動での削除はいたちごっこです。最もクリーンで効果的な解決策は、ファイルシステムレベルで不変フラグを設定し、カーネルレベルで書き込みアクセスを拒否することです。
macOS
# チェックポイントディレクトリをワイプしてロックする
rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
chflags uchg ~/.zcode/v2/checkpoints
# 検証:「Operation not permitted」と出力されるはずです
touch ~/.zcode/v2/checkpoints/test
Linux
# チェックポイントディレクトリをワイプしてロックする
rm -rf ~/.zcode/v2/checkpoints