Автор не является носителем английского языка; эта статья была переведена с помощью ИИ.
Всё началось с рутинной проверки при очистке диска: ~/.zcode занимал более 700 МБ. После промежуточных исследований выяснилось что-то поистине впечатляющее:
Каждый раз, когда вы авторизованы в ZCode (официальном приложении для кодирования с ИИ от Zhipu), оно бесшумно упаковывает весь ваш рабочий проект — полную историю .git, кеш LFS-активов, reflogs и глобальные конфиги приложения — шифрует и загружает прямо в Aliyun OSS.
Ещё более иронично: RSA-ключ, используемый для шифрования, поставляется сервером в реальном времени, а приватный ключ хранится исключительно в облаке. Вы не можете расшифровать этот многосотмегабайтный шифротекст, лежащий прямо на вашем диске, и ZCode тоже не может.
Вот полная запись расследования, цепь доказательств и однострочная защита, которая это блокирует навсегда.
Начало: 313 МБ архива в статусе ожидания
~/.zcode — корневая директория данных ZCode. Распределение размера выглядело примерно так:
cli/: ~257 МБ (базы данных сессий, логи выполнения)computer-use/: ~130 МБ (упакованное приложение и зависимости runtime)v2/checkpoints/: ~303 МБ (главный подозреваемый)
Внутри v2/checkpoints/ нашлось 313 МБ файл .enc рядом с файлом метаданных состояния:
{
"workspacePath": "/Users/ferstar/myprojects/<commercial project>",
"lastCompressedSize": {
"encryptedSizeBytes": 313070842,
"workspaceSizeBytes": 345549173
},
"kind": "baseline",
"failureCount": 564
}
История была простая:
- Клиент сканировал активный коммерческий проект, исключал
node_modulesи некоторые другие, упаковав оставшиеся 345 МБ в 313 МБ зашифрованный архив с меткойbaseline(полный снимок); - Записал 564 неудачные попытки загрузки, оставив его в локальной директории
pending/в ожидании следующей повторной попытки.
Репозиторий в целом занимал 10 ГБ; минус зависимости, оставшиеся 345 МБ — почти полностью интеллектуальная собственность.
Путь данных: от логов к обратному инжинирингу asar
Логи не содержали явных URL загрузки, поэтому был распакован файл app.asar клиента. Восстановленный поток загрузки:
sequenceDiagram
participant C as ZCode client
participant S as zcode.z.ai
participant O as Aliyun OSS
C->>S: POST /api/v1/snapshot/upload-credential
S-->>C: snapshot_id + RSA public key + max_size + OSS form credentials + callback
C->>C: tar.gz pack → AES-256-CTR encrypt → RSA-OAEP wrap key
C->>O: PostObject direct upload of tar.gz.enc
O->>S: callback confirms receipt
Конвейер работает в два этапа:
- Запрос учётных данных у координатора: клиент вызывает
https://zcode.z.ai(VITE_ZCODE_ENDPOINT_ORIGINв коде). Сервер возвращает подписи OSS форм (policy,x-oss-signature), динамический Object Key, ограничения размера и RSA-ключ для этого раунда шифрования; - Прямая отправка формы в OSS: после архивирования и потокового шифрования локально, клиент обходит собственные серверы приложений ZCode и отправляет
tar.gz.encпрямо в Aliyun OSS через HTTP POST форму. OSS затем совершает обратный вызов в backend Zhipu для регистрации снимка.
Проверка активных сокетов подтвердила: работающий процесс ZCode поддерживал постоянные HTTPS-соединения с IP-адресами zcode.z.ai плюс два узла хранения Aliyun OSS.
Самая ироничная часть: ключ принадлежит серверу
Реализация шифрования использует классическое конвертное шифрование:
keyId: String(i.encryption.key_version),
keyWrapAlgorithm: "rsa-oaep-sha256",
publicKeySpkiPem: Ylt(i.encryption.public_key)
- Содержимое шифруется с использованием эфемерного симметричного ключа через AES-256-CTR;
- Симметричный ключ оборачивается с использованием RSA-OAEP-SHA256 с публичным ключом, поставляемым сервером.
Критический момент — этот публичный ключ: он передаётся сервером во время согласования учётных данных, а соответствующий приватный ключ никогда не попадает на ваше устройство. Распаковка конверта с использованием всех локальных приватных ключей в системе не удалась, как и ожидалось.
Иными словами: тот 313 МБ шифротекста на вашем диске не может быть открыт ни вами, ни клиентом. Только backend Zhipu держит ключ для его открытия.
Если бы эта функция была действительно разработана для откатов с стороны пользователя или синхронизации между устройствами, ключи жили бы локально (как в Git или Time Machine). Ключ, который только сервер может использовать, служит ровно одной цели: позволить серверу читать ваш код когда угодно.
Что упаковывается: почти 90% это .git
Хотя шифротекст заблокирован, Manifest (инвентарь файлов), созданный во время упаковки, сохраняется локально в открытом виде. Разбор снимка из 42 411 файлов:
| Содержимое | Размер | Доля | Содержащаяся информация |
|---|---|---|---|
.git/lfs/ |
196,1 МБ | 56,8% | Кеш LFS — все когда-либо загруженные бинарные активы и медиа |
.git/objects/ |
102,2 МБ | 29,6% | Полное хранилище объектов истории коммитов (коммиты, деревья, блобы) |
.git/logs/ |
0,6 МБ | 0,2% | reflogs — локальная история веток и незапушенные операционные следы |
| Исходный код и документация | ~46,2 МБ | 13,4% | src/, конфиг-файлы, внутренняя документация |
Директория .git одна занимает 86,6% полезной нагрузки.
После загрузки облако получает намного больше, чем просто ваше текущее рабочее дерево — оно получает всю родословную вашего репозитория с самого первого дня:
- Исторические API-ключи и конфиги, удалённые в более поздних коммитах;
- Незапушенные локальные имена веток (выявляющие планы нереализованных функций);
- Внутренние имена хостов GitLab и пути репозиториев, конфигурированные в
.git/config.
Кроме того, дополнительный manifest с названием repo_snapshot_extra_manifest хеширует ваши глобальные конфиг-файлы ZCode (такие как settings.behavior.json) и упаковывает их поверх рабочих пространств с каждым снимком.
Правда о переключателях: UI тоггл их не остановит
Естественная реакция — проверить настройки, чтобы отключить это. Был проведён перекрёстный анализ UI опций с кодовой базой:
| Переключатель | Что вы ожидаете | Что на самом деле делает |
|---|---|---|
Оптимизация опыта (optimizeAgentExperienceEnabled) |
Отключает телеметрию / сбор данных | Только управляет разрешением на использование данных для обучения модели. Захват и загрузка снимков продолжаются |
Индексирование снимков репозитория (repoSnapshotIndexingEnabled) |
Отключает функцию снимков | Только управляет индексированием сервером загруженных снимков. Локальная упаковка и загрузка продолжаются без перерыва |
При взгляде на ассемблерный код хоста становится кристально ясно: боковой процесс захвата/загрузки инстанцируется безусловно при запуске. Нет никаких проверок if на предпочтения пользователя; единственное требование — чтобы tokenProvider мог вернуть валидный JWT.
Итого: пока вы авторизованы, этот фоновый конвейер постоянно активен, и никакая UI настройка его не отключит.
Захват происходит в две точки: captureBeforePrompt (перед каждым запросом) и при завершении задач с меткой repo-wiki-update. В логах сессии одна активная сессия генерировала до 62 событий захвата.
Что говорит Политика конфиденциальности
В политике конфиденциальности ZCode явно сказано, что собирается «текст, файлы и код, отправленные во время диалогов» — стандартная практика для предоставления контекста LLM.
Однако во всей политике, FAQ и логах изменений нет ни единого упоминания о скрытой упаковке и загрузке полных рабочих пространств и полных историй Git.
Ближайшее упоминание — это общее заявление шаблона: «программа оптимизации отключена по умолчанию, и входные данные не будут использоваться для обучения без согласия».
Защита: удаление — это игра в бита, заблокируйте директорию
Когда был первый раз найден пакет в ожидании, он был просто удалён. В течение получаса — пакет пересоздан — свежий 313 МБ архив с счётчиком повторов, прыгающим с 564 на 565. Когда загрузчик видит, что файл исчез, он просто упаковывает новый. Ручное удаление — игра в бита.
Самое чистое и эффективное решение — установить флаг неизменяемости на уровне файловой системы, отрицая доступ на запись на уровне ядра:
macOS
# Wipe and lock the checkpoints directory
rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
chflags uchg ~/.zcode/v2/checkpoints
# Verify: should output "Operation not permitted"
touch ~/.zcode/v2/checkpoints/test
Linux
# Wipe and lock the checkpoints directory
rm -rf ~/.zcode/v2/checkpoints
mkdir -p ~/.zcode/v2/checkpoints
sudo chattr +i ~/.zcode/v2/checkpoints
# Verify: should output "Operation not permitted"
touch ~/.zcode/v2/checkpoints/test
Влияние и откат
- Результат: логика захвата блокируется ядром при каждой попытке дисковой I/O. Без локальных артефактов конвейер загрузки нечему отправлять;
- Компромисс: UI функция «откат контрольной точки / временная шкала» не будет работать (что всегда требовало загрузку вашего кода в первую очередь). Обычный чат, автодополнение и выполнение инструментов работают без проблем. Поглощённые ошибки I/O в логах безвредны;
- Для восстановления: запустить
chflags nouchg ~/.zcode/v2/checkpoints(macOS) илиsudo chattr -i ~/.zcode/v2/checkpoints(Linux).
Заключительные мысли
При использовании инструментов ИИ, вывод модели неизбежно нуждается в контексте кода — все это понимают. Но такое поведение явно переступает черту двумя способами:
Во-первых, объём данных. Вывод отправляет контекст, релевантный задаче; снимкирование экспортирует весь репозиторий вместе с годами истории коммитов Git.
Во-вторых, архитектурная позиция. Если бы это было действительно разработано для восстановления с пользовательской стороны или синхронизации, ключи расшифровки принадлежали бы пользователю. Ключ шифрования, принадлежащий исключительно серверу, нулевое раскрытие в политиках конфиденциальности, неостановимые фоновые загрузки и упорное переупаковывание при удалении — это выглядит меньше как резервная копия и намного больше как сбор.
Инструменты есть инструменты, но пользователи должны сами провести свои границы. Если программное обеспечение не позволяет вам его отключить, используйте ядро ОС, чтобы заблокировать его в клетке.