파일 42,411개를 묶어 올렸고 그중 86.6%가 .git이었다
ZCode 스냅샷 해부
AI 코딩 앱 ZCode가 로그인 상태에서 워크스페이스와 git 이력 전체를 압축해 클라우드로 올린다. 설정의 스위치 두 개는 이 동작을 끄지 않는다. 복호화 키는 서버만 갖는다.
디스크를 정리하다 ~/.zcode 폴더가 700MB를 넘은 것을 본 개발자가 클라이언트를 뜯었다. 안에는 313MB 짜리 암호화 파일 하나와 업로드 실패 564회가 적힌 상태 파일이 있었다. 압축 전 원본 크기는 345MB, 대상은 그가 작업하던 상업 프로젝트였다.
ZCode는 Zhipu가 내놓은 AI 코딩 데스크톱 앱이다. 역공학 기록은 2026년 9월 18일에 공개됐다. 요지는 짧다. 로그인해 두면 워크스페이스 전체를 .git 이력까지 묶어 압축·암호화하고 Aliyun OSS로 올린다.
한 줄 정리
ZCode가 로그인 상태에서 워크스페이스와 git 이력 전체를 압축해 외부 저장소로 올리고 그것을 여는 키는 서버만 갖는다.
한눈에 보기
암호문은 못 열지만 압축할 때 만든 파일 목록이 평문으로 로컬에 남는다. 파일 42,411개 스냅샷의 구성은 이렇다.
| 내용 | 크기 | 비중 |
|---|---|---|
.git/lfs/ |
196.1MB | 56.8% |
.git/objects/ |
102.2MB | 29.6% |
.git/logs/ |
0.6MB | 0.2% |
| 소스·문서 | 46.2MB | 13.4% |
.git만 합쳐 86.6%다. 지금 작업 중인 파일이 아니라 첫 커밋부터의 이력 전체가 나간다는 뜻이다. 그 안에는 나중에 지운 API 키, 푸시하지 않은 로컬 브랜치 이름, .git/config에 박힌 내부 GitLab 호스트명이 같이 있다. 브랜치 이름만으로도 미공개 기획이 읽히는 팀이 적지 않다.
스위치가 두 개 있는데 둘 다 이걸 안 끈다
설정 화면에는 끌 수 있어 보이는 항목이 둘 있다. 코드와 맞춰 보면 하는 일이 다르다.
| 스위치 | 기대되는 동작 | 실제 동작 |
|---|---|---|
| Optimize Experience | 데이터 수집 중단 | 학습 사용 동의 여부만 제어 |
| Repo Snapshot Indexing | 스냅샷 기능 중단 | 서버 색인 여부만 제어 |
캡처와 업로드를 담당하는 모듈은 앱이 시작할 때 조건 없이 생성된다. 사용자 설정을 보는 분기문이 없다. 유효한 토큰만 있으면 돈다. 캡처 시점은 프롬프트를 보내기 직전과 작업이 끝난 뒤이고 한 세션에서 캡처 이벤트가 62회까지 찍혔다.
대기 중인 압축 파일을 지워 봐도 30분 안에 다시 만들어졌다. 재시도 카운터가 564에서 565로 올랐다. 파일이 없으면 새로 묶는 구조라 손으로 지우는 방식은 끝이 없다. 결국 커널에서 막는 쪽으로 갔다. macOS는 chflags uchg, Linux는 chattr +i로 해당 디렉터리에 쓰기를 봉했다.
열쇠가 서버에 있다
암호화 방식 자체는 교과서적이다. 본문은 AES-256-CTR, 대칭키는 RSA-OAEP-SHA256으로 봉한다. 걸리는 대목은 그 공개키의 출처다. 서버가 회차마다 내려주고 대응하는 개인키는 클라우드에만 있다. 내 디스크에 놓인 313MB를 나도 클라이언트도 열지 못한다.
롤백이나 기기 간 동기화를 위한 기능이라면 키가 로컬에 있어야 맞다. Git도 Time Machine도 그렇게 동작한다. 서버만 열 수 있는 방식은 목적이 다르다.
스냅샷에는 워크스페이스 말고도 한 겹이 더 붙는다. 전역 설정 파일을 해시로 떠서 스냅샷마다 같이 실어 보낸다. 워크스페이스를 옮겨 다녀도 같은 사용자라는 연결이 유지되는 구조다.
개인정보처리방침에는 "대화 중 제출한 텍스트·파일·코드"를 수집한다고 적혀 있다. 추론에 맥락을 넣는 이상 당연한 문장이다. 방침과 FAQ, 변경 이력 어디에도 워크스페이스와 git 이력을 통째로 묶어 올린다는 설명은 없다. Zhipu 쪽은 이후 ZCode를 오픈소스로 공개하고 외부 감사를 받겠다고 밝혔다.
어디까지가 정상인가
이 사건을 "AI 도구는 데이터를 가져간다"로 뭉개면 판단이 안 선다. 추론에 맥락을 넣으려면 파일 내용이 서버로 가야 한다. 열어 둔 파일, 참조한 함수, 프롬프트에 붙인 문서. 이건 기능의 전제이고 쓰는 쪽도 알고 쓴다.
선이 갈리는 지점은 두 곳이다. 하나는 범위다. 작업에 필요한 맥락과 레포 전체 이력은 같은 것이 아니다. 지금 고치는 파일 세 개를 보내는 일과 3년치 커밋을 묶어 올리는 일 사이에는 목적의 차이가 있다. 다른 하나는 열쇠의 위치다. 내 기기에 있는 암호문을 내가 못 여는 구조는 백업으로 설명되지 않는다.
이 두 기준은 계약서 문구보다 실용적이다. 도구 도입을 결정할 때 물어볼 질문이 두 줄로 줄어든다. 무엇을 보내나, 그리고 그걸 누가 열 수 있나. 답이 문서에 없으면 그 자체가 답이다.
위시클레이 관점
도구를 고를 때 물어 온 질문이 "설정에서 끌 수 있나"였다. ZCode에는 스위치가 둘 있었고 둘 다 아무것도 끄지 않았다. 질문을 "끄면 실제로 꺼지나"로 바꿔야 한다. 그런데 방침 문서를 읽어도 그 답이 안 나온다. 이 건도 방침에는 아무 힌트가 없었고 디스크 용량에서 드러났다.
그래서 확인 지점을 문서에서 파일 시스템으로 옮긴다. 새 AI 도구를 팀에 깔 때 설치 직후와 하루 뒤 데이터 폴더 용량을 두 번 재 본다. 수백 MB가 쌓였으면 무엇이 쌓였는지 물을 근거가 생긴다. 이 사건도 700MB에서 시작했다.
.git이 짐의 86.6%를 차지했다는 사실은 순서 하나를 앞으로 당긴다. 도구 정책을 고치는 일보다 키를 돌리는 일이 빠르다. 커밋 이력에 토큰이나 접속 정보가 들어간 적 있는 레포라면 그 키는 이미 나갔다고 보고 교체부터 한다. 이력은 지워도 이미 올라간 스냅샷에서 지워지지 않는다.
여기서 팀 규모는 방어력이 아니다. 5명짜리 에이전시가 클라이언트 세 곳의 레포를 한 노트북에서 만지는 구성이 오히려 더 위험하다. 스냅샷은 워크스페이스 단위로 잡히고 전역 설정 파일은 워크스페이스마다 같이 묶인다. 한 번 잘못 고른 도구가 계약서 세 장을 동시에 건드린다.