Grok CLI가 xAI로 조용히 보내는 것: AI 코딩 에이전트 데이터 유출의 와이어레벨 해부
Grok CLI가 xAI로 조용히 보내는 것: AI 코딩 에이전트 데이터 유출의 와이어레벨 해부
AI 코딩 에이전트는 우리의 코드와 파일을 얼마나, 어디로 보내고 있는가 — 편의를 위한 텔레메트리인가, 통제 불능의 데이터 유출인가.
도입
클라우드 기반 AI 코딩 에이전트는 태생적으로 사용자의 코드를 서버로 전송한다. 원격 모델이 코드를 읽고 수정하려면 그 코드가 모델이 도는 서버에 도달해야 하기 때문이다. 이 사실 자체는 새롭지 않고, 논쟁의 대상도 아니다. 문제는 언제나 정도와 목적지, 그리고 고지 여부에 있다. 얼마나 보내는가, 어디에 얼마나 오래 남기는가, 사용자가 그것을 알고 동의했는가. 이 세 질문에 대한 답이 벌어질수록 “작업을 위한 필수 컨텍스트 전송”은 “통제되지 않는 데이터 유출”로 미끄러진다.
2026년 7월 12일(KST) 해커뉴스 1위에 오른 글은 바로 그 미끄러짐을 실측한 보고서다. cereblab이라는 필명의 분석가가 xAI의 Grok build CLI(테스트 버전 grok 0.2.93)를 mitmproxy로 중간자 가로채기 하여, 이 도구가 실제로 회선(wire)에 무엇을 흘려보내는지를 바이트 단위로 해부했다. 538포인트, 229개의 댓글이 달린 이 스레드의 결론은 단순하지 않다. Grok CLI는 에이전트가 읽은 파일뿐 아니라 저장소 전체를 git 히스토리까지 포함해, 에이전트가 그 파일을 실제로 읽었는지와 무관하게 통째로 업로드한다. .env 같은 시크릿 파일도 아무런 마스킹 없이 그대로 전송된다. 목적지는 구체적 이름을 가진 Google Cloud Storage 버킷이며, 이 동작은 기본값으로 켜져 있다. 그리고 사용자가 설정에서 끌 수 있는 옵트아웃은 “모델 학습”에만 관여할 뿐, 코드 업로드 자체는 막지 못한다.
며칠 뒤 이 이야기는 한 단계 더 나아간다. 한 사용자가 홈 디렉터리에서 Grok CLI를 실행하자, 도구가 repo_path를 홈 디렉터리 전체로 잡고 그 내용을 GCS로 밀어 올린 사건이 별도 스레드(“Grok CLI uploaded the whole home directory to GCS”, 438포인트)로 올라온 것이다. 편의를 위한 텔레메트리라는 명분과, 홈 디렉터리 통째 업로드라는 현실 사이의 거리가 이 글의 주제다. 아래에서는 먼저 와이어레벨 분석이 실제로 무엇을 증명했는지 확인하고(현상), AI 에이전트 텔레메트리의 구조와 그것이 무너뜨리는 신뢰 모델을 분석한 뒤(심층), 실무자가 지금 취할 수 있는 선택지와 이 사건이 남긴 시사점을 정리한다(전망).
현상: 와이어레벨 분석이 밝힌 것
분석의 방법론부터 짚어야 한다. cereblab의 접근은 추측이 아니라 재현 가능한 캡처에 기반한다. mitmproxy의 로컬 신뢰 CA를 macOS 키체인에 심어 TLS를 프록시에서 종단하고, HTTPS_PROXY와 SSL_CERT_FILE 환경변수를 걸어 Grok의 모든 HTTPS 트래픽을 복호화된 상태로 관찰했다. 여기에 각 파일마다 고유 마커를 심은 “카나리(canary)” 저장소를 사용했다. CANARY7F3A9-SECRET, CANARY-XR47P2-NEVERREAD 같은 유일 문자열이 캡처된 트래픽에 등장하면, 그 바이트가 정확히 어느 파일에서 유래했는지 반박 불가능하게 추적된다. 모든 캡처는 저자 본인의 기계에서 본인의 트래픽을 대상으로 했고, 시크릿은 전부 가짜 카나리였으므로 실제 자격증명이 노출된 사건은 아니다.
이 캡처가 드러낸 전송 경로는 두 개의 채널로 나뉜다. 채널 A는 모델 추론 요청, 즉 POST cli-chat-proxy.grok.com/v1/responses로 나가는 모델 턴이다. 여기에는 에이전트가 읽은 파일의 내용이 "messages":[…]"model":"grok-4.5" 형태의 JSON에 직렬화되어 실린다. 채널 B는 코드베이스 스냅샷 업로드, 즉 POST /v1/storage로 나가는 별도의 저장 경로이며, 이 엔드포인트가 실제로 라우팅하는 목적지가 grok-code-session-traces라는 이름의 GCS 버킷이다. 두 채널은 목적도 부피도 다르다.
첫 번째 발견은 채널 A에 관한 것이다. Grok이 어떤 파일을 읽으면 그 내용이 마스킹 없이 xAI로 전송된다. 저자는 48,070바이트짜리 복호화된 /v1/responses 요청 본문에서 API_KEY=CANARY7F3A9-SECRET-should-not-leave와 DB_PASSWORD=CANARY7F3A9-DBPASS를 그대로 추출해 보였다. .env나 secrets.env도 여느 소스 파일과 똑같이 취급되어, 시크릿 형태의 키에 대한 어떤 리댁션(redaction)도 관찰되지 않았다. 다만 저자는 여기서 정직한 범위 제한을 둔다. “파일을 읽지 마라”는 프롬프트를 준 통제 실험에서, 읽지 않은 파일의 마커는 채널 A 본문에 등장하지 않았다. 즉 채널 A의 누출은 에이전트가 실제로 읽은 파일에 한정되며, 결함의 핵심은 “읽는 행위”가 아니라 “시크릿 파일을 마스킹 없이 전송한다”는 데 있다.
두 번째 발견이 이 보고서의 무게중심이다. 채널 B는 에이전트가 무엇을 읽었는지와 무관하게 저장소 전체를 업로드한다. 이를 증명하기 위해 저자는 실제 cereblab_api 저장소에 CANARY-XR47P2-NEVERREAD-UNIQUE라는 마커를 심은 src/_probe/never_read_canary.txt를 넣고, “정확히 OK라고만 답하고 어떤 파일도 읽거나 열지 마라”는 프롬프트로 실행했다. 그럼에도 Grok은 저장소 전체를 **git 번들(bundle)**로 묶어 POST /v1/storage → 200으로 업로드했고, 캡처된 번들을 git clone하자 열지 말라고 지시한 그 파일이 마커까지 그대로, 게다가 4개 커밋의 전체 git 히스토리와 함께 복원됐다. 이 실험은 별개의 Cloudflare Worker 저장소(cereblab_auth)에서도 동일하게 재현되어, 그쪽의 미열람 카나리 역시 verbatim으로 복구됐다.
부피의 비대칭은 이 주장을 더 못박는다. 100% 무작위 파일로 채워 에이전트가 아무것도 읽지 않은 12GB 저장소를 대상으로 한 캡처에서, 채널 A(/v1/responses)의 총 전송량은 196,705바이트(약 192KB)에 그친 반면, 채널 B(/v1/storage)는 5,476,228,005바이트, 즉 5.10GiB를 밀어 올렸다. 약 27,800배의 격차다. 모델이 192KB로 5GiB어치 내용을 삼킬 수는 없으므로, 이 5.10GiB는 모델이 읽은 것이 아니라 저장소 스냅샷 그 자체다. 전송은 저장소 크기가 커질수록 전략을 바꿔가며 계속됐다. 수백 MB 구간에서는 수 MB 단위 청크로, 3GB 구간에서는 POST /v1/storage/multipart/init 이후 PUT storage.googleapis.com/grok-code-session-traces/multipart/<id>로 GCS에 직접 PUT하는 방식으로, 12GB 구간에서는 약 75MB짜리 청크 73개(5.10GiB)가 전부 HTTP 200으로 성공했다. 실패한 저장 요청은 하나도 없었고, 유일한 비(非)200 응답은 모델 엔드포인트 쪽의 사용량 쿼터(402 Payment Required, 429 Too Many Requests)뿐이었다. 다시 말해 코드 업로드에는 크기 상한이 없고, 상한이 걸리는 것은 모델 사용량뿐이다.
세 번째 발견은 목적지와 지속성이다. 바이너리에서 strings로 추출한 문자열에는 grok-code-session-traces, storage.googleapis.com, 그리고 “Uploading bytes to GCS via proxy”가 등장하며, 업로드 기계장치는 xai-data-collector라는 1st-party Rust 크레이트다. 업로드 직전 스테이징된 metadata.json은 각 파일에 gs://grok-code-session-traces/repo_changes_dedup/v2/…/sha256_… 형태의 목적지 fileId를 부여한다. 즉 트리 전체가, 읽지 않은 파일까지 포함해, 콘텐츠 주소화되어 이름이 명시된 GCS 버킷으로 올라간다. 이것은 전송 후 휘발되는 컨텍스트가 아니라 특정 버킷에 지속(persist)되는 데이터다.
옵트아웃이 이 흐름을 멈추지 못한다는 점이 마지막 발견이다. 저자가 설정에서 “Improve the model”을 끄고 재실행했을 때에도 Grok은 여전히 저장소 전체를 git 번들로 올렸고, 서버가 CLI에 내려주는 /v1/settings 응답은 그대로 "trace_upload_enabled": true, "upload_enabled": true, "session_registry_enabled": true(그리고 파일당 상한 "max_upload_file_bytes": 1073741824, 1GiB)를 반환했다. 학습 동의를 끄는 스위치와 코드 업로드를 끄는 스위치가 애초에 다른 것이다. 해커뉴스에서 한 사용자는 원문을 인용하며, 읽은 파일의 내용을 시크릿 .env까지 포함해 검열 없이 그대로 xAI에 전송하는 이 동작이 “역사상 가장 성공적인 대규모 감시 캠페인”에 가깝다는 취지로 지적한다. 또 다른 사용자는 “저장소 전체가, 에이전트가 무엇을 읽는지와 무관하게, 콘텐츠와 git 히스토리째로 업로드된다”는 문장을 그대로 옮기며 일론 머스크라면 후발주자를 따라잡기 위해 이런 짓을 할 법하다고 예상은 했으나 이 정도는 대단히 우려스럽다는 취지로 지적한다.
심층: AI 에이전트 텔레메트리의 구조와 신뢰 문제
기술적으로 저장소 전체를 올리는 데에는 나름의 이유가 있을 수 있다. 해커뉴스의 한 논평자는 모델이 “사고(thinking)” 단계에서 클라이언트로 되돌아가 실제 도구 호출을 하지 않고도 서버 측에서 코드베이스를 직접 들여다볼 수 있게 하려는 것이 하나의 동기일 수 있다는 취지로 지적한다. 라운드트립을 줄이면 지연시간이 짧아지고 에이전트의 성능이 좋아진다. 이것이 네이티브 런너가 API를 직접 호출하는 오픈 하네스보다 빠르고 매끄러운 이유이기도 하다. 여기에 명백한 트레이드오프가 있다. 저장소를 서버에 미리 통째로 올려두면 능력과 속도를 얻는 대신, 사용자의 코드 전량이 벤더의 저장소에 상주하게 된다. 문제는 이 트레이드오프의 한쪽 항을 사용자가 알지도, 선택하지도 못한 채 기본값으로 강제당했다는 데 있다.
저자 스스로가 이 지점에서 반론을 선취한다. “클라우드 AI 도구가 컨텍스트를 보내는 것은 정상이다”라는 반박은 옳고, 그도 인정한다. 어떤 클라우드 에이전트든 작업을 하려면 코드를 서버로 보내야 한다. 그가 지적하는 새로운 세 가지 델타는 다음이다. 첫째, 시크릿 파일이 마스킹 없이 전송된다. 둘째, 그 내용이 일시적으로 처리되고 마는 것이 아니라 이름이 명시된 GCS 버킷에 지속 저장된다. 셋째, 이 업로드 메커니즘이 CLI의 설치 스크립트나 퀵스타트 자료에 드러나 있지 않으며 기본값으로 켜져 있다. xAI의 소비자 약관은 모델 개선을 위한 데이터 사용을 넓게 고지하고 옵트아웃을 제공하지만, “학습에 데이터를 쓴다”는 포괄적 고지가 “당신의 저장소가 grok-code-session-traces로 상시 업로드된다”는 구체적 메커니즘의 고지와 같지는 않다. 넓은 고지와 구체적 고지의 이 간극이 신뢰 문제의 출발점이다.
더 깊은 층위의 문제는 벤더가 제공하는 통제 수단이 실제 피해와 어긋난다(orthogonal)는 점이다. 사건 이후 xAI가 추가한 /privacy 옵트아웃을 저자가 와이어로 검증해 보니, 그것은 전송된 데이터의 **보존(retention)**을 조정하는 설정일 뿐 무엇이 **전송(transmission)**되는지를 막는 스위치가 아니었다. 사용자가 손에 쥔 레버는 “덜 오래 보관한다”인데, 정작 우려의 본체는 “애초에 나간다”에 있다. 통제 수단의 의미론과 피해의 의미론이 맞물리지 않을 때, 옵트아웃 UI는 안심을 판매하되 위험은 그대로 남긴다. 이것이 “편의를 위한 텔레메트리”가 “통제 불능의 유출”로 미끄러지는 정확한 지점이다. 통제가 형식적으로 존재하되 실효적으로 작동하지 않는 구조.
여기에 프라이버시와 무관한 신뢰성 결함도 겹친다. 저자는 ~/.grok/upload_queue가 매 턴마다 수 GB 규모의 스냅샷을 스테이징하며, 부하 상황에서는 수십 GB로 불어나 디스크를 고갈시킬 수 있다고 보고한다. 데이터가 나간다는 사실과 별개로, 나가기 위해 로컬에 쌓는 큐 자체가 사용자의 디스크를 위협하는 버그다. 도구가 사용자의 자원을 사용자의 통제 밖에서 소비한다는 점에서, 이것은 프라이버시 문제와 같은 뿌리를 공유한다.
그리고 며칠 뒤의 에스컬레이션이 이 구조의 최악을 실연했다. 한 사용자가 홈 디렉터리에서 Grok CLI를 돌렸고, repo_path가 홈 디렉터리 전체로 잡히면서 그 내용이 GCS로 올라갔다. 해커뉴스의 반응은 둘로 갈렸다. 한쪽은 도구를 신뢰의 문제로 본다. 한 논평자는 원격 추론을 쓰는 어떤 AI 에이전트든 기본적으로 무엇이든 읽을 것이라 가정해야 하며, 하위 디렉터리로 “제한”했다 해도 그 가정을 유지해야 한다는 취지로 지적한다. 다른 한 논평자는 이런 도구들이 네이티브·독점 런너 형태로 배포되기에 프라이버시 측면에서 위험하며, 다음 업데이트에 어떤 “비밀 소스”가 추가될지 알 수 없으므로 opencode 같은 오픈 하네스로 API를 직접 호출하는 편이 안전하다고, 다만 그 경우 네이티브 런너만큼의 성능은 결코 나오지 않는다는 트레이드오프를 함께 짚는 취지로 지적한다. 또 다른 이는 이런 도구를 VM 안에서 돌리는 이유가 여기 있으며, curl … | bash에는 (정당하게) 회의적인 사람들이 어째서 이런 도구는 그토록 순순히 받아들였는지 의아하다는 취지로 지적한다.
다른 한쪽은 책임을 사용자에게 돌린다. 홈 디렉터리에서 에이전트를 돌린 것 자체가 실수이며, 원격 추론을 쓰는 이상 코드베이스에 대한 완전한 컨텍스트를 주는 것이 애초의 요점 아니냐는 반론이다. 이 두 진영의 대립은 결국 신뢰 모델의 문제로 수렴한다. 오늘날 코딩 에이전트를 쓴다는 것은 하네스, 모델, 프로바이더 각각에 자신의 컴퓨터·코드·사업을 맡긴다는 뜻이며, 좋은 상태는 아니지만 현실이 그러하니 신중히 고르라는 논평이 이 지점을 요약한다. 다만 “신중히 고르라”는 조언은 도구가 무엇을 하는지 사용자가 검증할 수 있을 때에만 실효를 가진다. 설치 자료에 드러나지 않고 기본값으로 켜진 메커니즘 앞에서, 대부분의 사용자에게 “신중한 선택”의 근거는 애초에 존재하지 않았다. cereblab의 보고서가 가치 있는 이유는 바로 그 검증의 근거를 회선에서 직접 길어 올렸다는 데 있다.
전망·실무 시사점
xAI의 대응은 압력이 실효를 낳았음을 보여준다. 원문의 2026-07-14 갱신에 따르면, xAI는 문제의 업로드를 서버 측에서 비활성화했고(disable_codebase_upload: true), 앞서 언급한 /privacy 옵트아웃을 추가했으며(단 저자의 검증 결과 이는 보존 설정에 그친다), 일론 머스크가 기존에 업로드된 데이터를 전부 삭제하겠다고 공개적으로 약속했다(삭제 완료는 아직 확인되지 않았다). 여기서 주목할 것은 수정의 형태다. 코드 변경이 아니라 서버 측 플래그의 전환으로 동작이 바뀌었다는 것은, 같은 방식으로 언제든 되돌릴 수 있음을 뜻한다. 사용자 신뢰의 관점에서, 리모트 플래그로 통제되는 기본 동작은 근본 해결이 아니라 유예에 가깝다.
실무자가 지금 취할 수 있는 통제 수단은 몇 갈래다. 가장 직접적인 것은 클라이언트 설정으로, 해커뉴스의 한 논평자는 ~/.grok/config.toml에 [harness] disable_codebase_upload = true를 넣는 것이 저장소 업로드를 끄는 가장 단순한 방법이라는 취지로 지적한다. 그러나 벤더가 제공하는 스위치를 신뢰할 수 없다면, 통제는 도구 바깥에 두어야 한다. 한 논평자는 코딩 도구를 LLM 프로바이더로부터 분리하고 bubblewrap으로 샌드박싱하여, 작업 디렉터리만 읽게 하고 .git을 읽기 전용으로, 민감 디렉터리는 빈 디렉터리로 마운트해 가리며, 격리된 네트워크 네임스페이스에서 HTTP 프록시를 통해서만 인터넷에 나가되 특정 LLM 프로바이더 호스트만 허용하고 도구 자신의 호스트는 배제하는 구성을 소개한다. 요컨대 프로바이더로 가는 트래픽은 허용하되 도구의 자체 텔레메트리 호스트로 가는 트래픽은 차단하는 것이다. 또 다른 이는 podman 컨테이너에서 에이전트를 돌리고 작업이 끝나면 git 패치만 호스트로 되돌리는 방식을, 다른 이들은 VM 격리를 제시한다. 공통 원칙은 명확하다. 절대 홈 디렉터리에서 에이전트를 돌리지 않고, 네트워크 반출 경로를 화이트리스트로 통제하며, 신뢰를 벤더의 약속이 아니라 자신이 검증 가능한 경계에 둔다.
규제의 층위도 남는다. 해커뉴스에서는 이런 무차별 코드 반출이 GDPR 같은 유럽 규제와 어떻게 양립하는지에 대한 의문이 제기됐다. 기업 사용자라면 코드가 학습에 쓰이지 않고 보존되지 않음을 계약으로 보장하는 ZDR(Zero Data Retention) 플랜의 존재 여부와 그 범위가 도구 선택의 1차 기준이 되어야 한다. 그러나 이 사건이 보여준 것은, 소비자 로그인 기본값에서는 그런 보장이 작동하지 않았다는 점이다. 개인 개발자와 소규모 팀은 계약적 보호의 사각지대에 놓인다.
더 넓게 보면 이 사건은 Grok만의 문제가 아니라 코딩 에이전트 전반의 거버넌스 문제다. 반복되는 안티패턴은 “기본값으로 켜짐 + 실효 없는 옵트아웃”이다. 기본값이 최대 수집이고, 사용자에게 주어진 옵트아웃이 정작 피해의 본체(전송·지속)를 건드리지 못한다면, 동의는 형식일 뿐이다. 반대 방향의 설계 원칙은 분명하다. 시크릿에 대한 클라이언트 측 리댁션을 기본으로 하고, 코드베이스 지속 업로드는 옵트인으로 전환하며, 메커니즘을 설치 자료에 명시하고, 도구의 동작을 사용자가 회선에서 감사(audit)할 수 있게 만드는 것이다. cereblab의 방법론 자체 — mitmproxy 중간자 캡처와 카나리 마커, 바이너리 문자열 조사, git 번들 복원 — 가 이 감사가 특수 기술이 아니라 누구나 재현할 수 있는 루틴이 되어야 함을 시사한다. 성능을 위해 네이티브 런너를 택하든 감사 가능성을 위해 오픈 하네스를 택하든, 그 선택은 도구가 무엇을 하는지 검증 가능할 때에만 합리적이다.
결론
리드 질문으로 돌아간다. 이것은 편의를 위한 텔레메트리인가, 통제 불능의 데이터 유출인가. 와이어레벨 증거가 가리키는 답은 후자에 훨씬 가깝다. 클라우드 에이전트가 코드를 전송한다는 사실 자체는 정상이지만, grok 0.2.93의 기본 동작에는 정상의 범위를 벗어나는 세 가지 델타가 있었다. 시크릿의 무마스킹 전송, 이름이 명시된 버킷으로의 지속 저장, 그리고 설치 자료에 없이 기본값으로 켜진 메커니즘이다. 결정적으로, 벤더가 제공한 통제 수단(학습 옵트아웃, 이후의 보존 설정)은 피해의 본체(전송과 지속)와 어긋나 있었다. 사용자가 끌 수 있는 것과 사용자가 끄고 싶은 것이 다를 때, “편의를 위한 텔레메트리”라는 프레임은 무너진다. 홈 디렉터리 통째 업로드는 그 붕괴의 극단적 예시였을 뿐이다.
이 사건이 남긴 실무적 교훈은 신뢰를 약속이 아니라 검증 위에 세우라는 것이다. 벤더의 옵트아웃이 무엇을 끄는지, 도구가 어떤 호스트로 무엇을 보내는지를 회선에서 확인할 수 없다면, 통제는 도구 바깥의 샌드박스와 네트워크 경계에 두어야 한다. xAI가 서버 플래그로 업로드를 껐다는 사실은 압력이 통했음을 보여주지만, 플래그로 켠 것은 플래그로 되돌릴 수 있다. 코딩 에이전트가 개발 워크플로의 기본 인프라가 되어가는 지금, 필요한 것은 벤더의 선의가 아니라 옵트인·리댁션·명시적 고지·회선 감사 가능성이라는 구조적 기본값이다. 트레이드오프는 늘 존재한다 — 네이티브 런너의 성능과 오픈 하네스의 감사 가능성, 기본 텔레메트리의 편의와 명시적 동의의 통제. 다만 그 트레이드오프를 사용자가 알고 선택할 수 있을 때에만, 그것은 트레이드오프라는 이름에 값한다.
출처:
- What xAI’s Grok build CLI sends to xAI: A wire-level analysis (cereblab) — https://gist.github.com/cereblab/dc9a40bc26120f4540e4e09b75ffb547
- Hacker News 토론 스레드 (objectID 48877371) — https://news.ycombinator.com/item?id=48877371
- 에스컬레이션: “Grok CLI uploaded the whole home directory to GCS” (objectID 48892468) — https://news.ycombinator.com/item?id=48892468
- xAI Privacy Policy — https://x.ai/legal/privacy-policy
- xAI Consumer Terms of Service — https://x.ai/legal/terms-of-service