xAI Grok Build CLI가 xAI로 전송하는 데이터: 와이어 수준 분석

1 day ago 4
  • grok 0.2.93의 네트워크 트래픽을 직접 캡처한 결과, Grok Build는 읽은 파일을 마스킹 없이 전송하고 session_state로 저장했으며 테스트용 .env 비밀값도 두 경로에 그대로 포함했음
  • 모델 요청이 에이전트가 읽은 파일을 보내는 것과 별개로, 모든 추적 파일과 Git 이력을 담은 저장소 전체가 git bundle로 업로드됐고 열지 말라고 지정한 파일도 원문 그대로 복구됐음
  • 12GB 무작위 파일 저장소에서 /v1/responses 요청은 총 192KB였지만 /v1/storage 전송량은 캡처 중단 시점까지 5.10GiB에 달해 약 27,800배 차이가 났으며, 모든 저장 요청이 HTTP 200을 반환했음
  • 업로드 목적지는 Google Cloud Storage의 grok-code-session-traces 버킷이었고, “Improve the model”을 꺼도 trace_upload_enabled: true와 upload_enabled: true가 유지되며 전체 저장소 업로드가 계속됐음
  • 실험은 데이터의 전송·수락·저장을 입증하지만 모델 학습에 사용됐는지는 확인하지 못했으며, .gitignore 파일과 모든 계정·설정 조합도 시험하지 않아 결과는 2026년 7월의 특정 버전에 한정됨

테스트 대상과 분석 범위

  • 대상은 일반 소비자 계정으로 로그인한 xAI 공식 Grok Build CLI였음
    • 설치 경로는 ~/.grok/bin/grok
    • 브라우저에서 X 또는 SuperGrok 계정으로 인증하며 API 키는 사용하지 않음
    • 테스트 바이너리는 Apple Silicon용 grok 0.2.93 (f00f96316d4b)임
    • SHA-256은 2a97ba675bd992aa9b981e2e83776460d94f469b510c0b8efe28b50d236d767c임
  • 바이너리 문자열에서 자체 Rust 업로드 구성요소와 저장소 관련 상수가 확인됐음
    • crates/codegen/xai-data-collector/src/gcs.rs
    • storage_client.rs, queue.rs, file_access_tracker.rs, circuit_breaker_observer.rs
    • xai-grok-shell/src/upload/{gcs,turn,trace,manifest}.rs
    • grok-code-session-traces, storage.googleapis.com, Uploading bytes to GCS via proxy
  • 모든 캡처는 테스트 수행자의 컴퓨터와 트래픽만을 대상으로 했으며, 저장소에는 실제 자격 증명 대신 고유 canary 문자열을 넣었음

재현 및 트래픽 캡처 방법

  • macOS Apple Silicon에서 mitmproxy의 CA를 로그인 키체인에 신뢰 인증서로 추가하고 HTTPS_PROXY와 SSL_CERT_FILE을 설정해 Grok의 HTTPS 요청을 캡처함
  • Grok은 이 구성에서 인증서 고정(certificate pinning) 으로 캡처를 차단하지 않았음
  • mitmdump 애드온으로 요청별 메서드, 호스트, 경로, 응답 상태, 요청 바이트 크기를 기록하고 xAI 호스트로 향하는 요청 본문을 저장함
  • ~/.grok/upload_queue/*를 실행 중 복사한 뒤 gzip과 tar로 풀어 전송 대기 중인 스테이징 아티팩트도 검사함
  • 각 파일에는 출처를 구분할 수 있는 고유 마커를 넣었으며, 비밀 파일에는 다음과 같은 가짜 값을 사용함
    • API_KEY=CANARY7F3A9-SECRET-should-not-leave
    • DB_PASSWORD=CANARY7F3A9-DBPASS

채널 A: 읽은 파일과 .env의 모델 요청 전송

  • Grok이 읽은 파일 내용은 POST cli-chat-proxy.grok.com/v1/responses의 모델 턴 본문에 직렬화됐음
    • 캡처된 48,070바이트 요청에는 "model":"grok-4.5"와 메시지 배열이 포함됐음
    • .env의 API_KEY와 DB_PASSWORD canary가 줄바꿈까지 원문 그대로 발견됐음
    • 소스, 로직, README, 중첩된 JavaScript 파일과 두 비밀값을 포함한 6개 마커를 요청 본문에서 복구할 수 있었음
  • 같은 내용은 session_state 아카이브에도 들어가 POST /v1/storage로 업로드됐으며, 해당 저장 요청은 HTTP 200으로 수락됐음
    • 압축 아카이브를 풀어 검사하자 두 비밀값과 다른 파일 마커가 모두 발견됐음
    • 비밀값은 모델이 일시적으로 처리하는 데 그치지 않고 저장용 아카이브에도 기록됐음
  • Reply exactly OK, do not read any files라는 통제 프롬프트에서는 열지 않은 untouched_secret.txt의 마커가 /v1/responses 본문에 나타나지 않았음
    • 이는 모델 요청 채널이 에이전트가 실제로 읽은 파일을 중심으로 구성된다는 범위만 입증함
    • 별도의 저장소 전체 스냅샷 채널에는 읽지 않은 파일도 포함되므로, 이 통제 결과가 전체 저장소 업로드를 부정하지는 않음
  • 비밀값은 .env 또는 secrets.env에 있었고 Git으로 추적되는 파일이었음
    • .gitignore에 포함된 파일도 업로드되는지는 별도로 시험하지 않았음
    • canary는 실제 고엔트로피 토큰 형식이 아니므로 sk-… 같은 특정 형태를 위한 별도 마스킹 장치가 전혀 없다고까지 입증하지는 않음
    • 확인된 범위는 테스트한 .env 내용이 마스킹되지 않은 채 전송됐다는 점임

채널 B: 읽지 않은 파일까지 포함한 저장소 전체 업로드

  • /v1/storage는 모델이 읽은 파일을 보내는 /v1/responses와 별개로 작업 공간 전체 스냅샷을 업로드했음
  • 실제 cereblab_api 저장소에서 Reply with exactly: OK. Do not read or open any files.라고 지시한 뒤 실행함
    • Grok은 POST /v1/storage로 152,102바이트 git bundle을 전송했고 서버는 HTTP 200을 반환했음
    • 캡처한 uploaded_repo.bundle을 git clone하자 열지 말라고 지정한 src/_probe/never_read_canary.txt가 복구됐음
    • 파일 안의 CANARY-XR47P2-NEVERREAD-UNIQUE 마커도 원문 그대로 남아 있었음
    • bundle에는 4개 커밋, 47개 파일과 전체 Git 이력이 포함됐음
  • 두 번째 독립 저장소 cereblab_auth에서도 같은 결과가 재현됐음
    • POST /v1/storage로 31,743바이트 git bundle이 업로드되고 HTTP 200을 반환했음
    • 복원된 저장소에서 읽지 않은 파일의 CANARY-AUTH-4T8K2-NEVERREAD 마커가 그대로 확인됐음
  • 별도의 298개 파일 규모 cereblab_api 실행에서는 스테이징된 manifest가 읽지 않은 파일까지 열거했음
    • src/_probe/never_read_canary.txt와 src/apns.ts, src/index.ts 등의 실제 소스가 함께 기록됐음
    • metadata.json의 파일별 fileId는 gs://grok-code-session-traces/repo_changes_dedup/v2/supplemental/sha256_… 형태였음
    • 이 실행은 할당량에 막혀 콘텐츠 blob 전송까지 완료하지 못했지만, 별도의 git bundle 실험과 12GB 실험에서 실제 업로드가 확인됐음
  • 바이너리에는 after_codebase.tar.gz, repo_state.upload, collecting workspace files, spawning background coordinator 같은 문자열도 포함돼 작업 공간 수집 메커니즘과 일치했음

12GB 저장소에서 확인한 다중 GiB 전송

  • 압축이 거의 되지 않는 무작위 파일로 저장소 크기를 늘리며 업로드 동작을 관찰함
    • 64MB에서는 약 48MB 크기의 단일 POST /v1/storage가 HTTP 200을 반환했지만 로그는 보존하지 못함
    • 약 600MB에서는 약 7.5MB 청크 수십 개가 모두 HTTP 200을 반환했으나 로그가 남아 있지 않음
    • 약 3GB에서는 multipart 초기화 후 storage.googleapis.com으로 50MB 단위 PUT이 관찰됐지만 직접 PUT 로그는 보존하지 못함
    • 약 12GB에서는 약 75MB 청크를 /v1/storage로 계속 전송했으며 5.10GiB 시점에서 캡처를 중단
  • 보존된 wire_12gb.log에는 /v1/storage* HTTP 200 응답이 총 83개 있음
    • 콘텐츠 업로드 POST /v1/storage 82개
    • 이 가운데 약 75MB 청크 73개
    • 73개 청크의 합계는 5,476,083,317B, 즉 5.10GiB 또는 5.48GB임
    • 작은 POST 9개와 /v1/storage/batch_exists 중복 확인 1개도 포함됨
    • 전체 /v1/storage* 요청 바이트는 5,476,228,005B이며 실패한 저장 요청은 없었음
  • 캡처는 업로드가 계속되는 중에 중단됐으므로 12GB 전체 업로드 완료를 입증하지는 않음
    • 마지막 기록도 약 75MB 청크에 대한 HTTP 200이었음
    • 입증된 범위는 5.10GiB 이상이 실패 없이 전송됐고 중단 시점에도 계속 증가하고 있었다는 것임
  • 같은 세션의 두 채널 사이에는 큰 용량 차이가 있었음
    • /v1/responses는 5개 요청을 합쳐 196,705B, 약 192KB였고 가장 큰 요청도 60,394B였음
    • /v1/storage는 5.10GiB였음
    • 27,800배 차이가 발생해 모델 요청에 포함되지 않은 대량의 저장소 데이터가 별도 저장 채널로 이동했음
  • 모델 할당량 오류와 저장 업로드는 독립적으로 발생했음
    • /v1/responses에서 HTTP 402가 1회, 429가 3회 발생함
    • 세션 bookkeeping 요청에서는 관련 없는 404가 1회 발생함
    • 첫 429 이후에도 /v1/storage HTTP 200이 76회 이어졌음
    • 저장 요청 82개는 모두 성공했으며 테스트 범위에서 저장 용량 제한 오류는 발생하지 않았음
  • 이 결과는 로컬 upload_queue가 비워지는 현상에 의존하지 않음
    • 큐는 업로드 성공과 폐기 모두에서 비워질 수 있어 증거로 사용하지 않았음
    • 근거는 요청 본문이 실제 네트워크로 나가 HTTP 200을 받은 캡처와 업로드된 git bundle에서 파일을 복구한 결과임

저장 위치와 원격 측정

  • 저장 목적지는 AWS S3가 아니라 Google Cloud Storage의 grok-code-session-traces 버킷으로 확인됐음
    • 바이너리에 grok-code-session-traces, storage.googleapis.com, Uploading bytes to GCS via proxy가 포함됐음
    • 보존된 metadata.json의 파일 목적지가 gs://grok-code-session-traces/…로 기록됐음
    • 약 3GB 실험에서는 해당 GCS 호스트로 직접 multipart PUT도 관찰했지만 로그는 보존하지 못함
    • 바이너리에 aws-sdk-s3가 포함돼 있어도 테스트에서 확인된 목적지는 GCS였음
  • 제3자 및 자체 원격 측정 요청도 확인됐음
    • Mixpanel의 api.mixpanel.com/track과 /engage
    • grok.com/_data/v1/events
    • 해당 요청은 모두 HTTP 200을 반환했음
  • 검토한 CLI 설치 스크립트와 quickstart 자료에서는 repo_state, session_state, ~/.grok/upload_queue, grok-code-session-traces 업로드를 찾지 못했음
    • 모든 xAI 문서와 도움말을 조사한 것은 아니므로 어디에도 문서화되지 않았다고 단정할 수 없음
    • 확인 가능한 범위는 CLI 자체 설정 자료에서 드러나지 않았다는 것임
  • ~/.grok/upload_queue는 한 턴에 약 3GB 스냅샷을 스테이징할 수 있고 부하가 높으면 수십GB까지 증가해 디스크를 소진할 수 있었음
    • 이는 업로드의 개인정보 문제와 별개인 신뢰성 문제

“Improve the model” 설정과 정책 범위

  • 클라우드 코딩 에이전트가 작업에 필요한 코드 문맥을 서버로 보내는 행위 자체는 필요한 동작임
  • 테스트에서 확인된 동작은 다음 세 가지로 구분됨
    • .env 같은 비밀 파일을 마스킹 없이 전송
    • 해당 내용을 명시된 GCS 버킷에 저장함
    • 저장소 전체 업로드가 검토한 CLI 설정 자료에 드러나지 않은 채 기본 활성화돼 있음
  • xAI 소비자 정책은 모델 개선을 위한 데이터 이용과 opt-out을 광범위하게 다루며, Private Chat은 자동 opt-out이고 opt-out은 소급 적용되지 않음
    • 관련 문서는 xAI Privacy PolicyConsumer ToS
    • 이러한 일반적 학습 정책은 특정 repo_state 및 GCS 업로드 파이프라인을 문서화하는 것과 같지 않음
  • “Improve the model”을 꺼도 업로드는 중단되지 않음

    • 설정을 끈 상태에서도 전체 저장소가 git bundle로 /v1/storage에 업로드됐고 HTTP 200을 반환함
    • git clone으로 읽지 않은 파일과 Git 이력을 복구할 수 있었음
    • CLI가 받은 /v1/settings에는 "trace_upload_enabled": true, "upload_enabled": true, "session_registry_enabled": true가 유지됐음
    • "max_upload_file_bytes": 1073741824로 파일당 1GiB 제한도 반환됐음
    • 테스트 결과에서 opt-out은 학습 여부를 제어하지만 저장소가 컴퓨터를 떠나 업로드·저장되는 동작은 차단하지 않았음

입증하지 않은 사항과 증거의 한계

  • 네트워크 캡처만으로는 xAI가 데이터를 모델 학습에 사용한다는 사실을 입증할 수 없음
    • 확인된 범위는 전송, HTTP 200 수락, 저장용 아카이브와 GCS 목적지임
  • 3GB 실행에서 관찰한 storage.googleapis.com/grok-code-session-traces 직접 PUT 로그는 덮어써져 보존되지 않았음
    • 다중 GiB 업로드 근거는 보존된 12GB 실행의 /v1/storage 로그와 버킷을 명시한 바이너리 및 metadata임
  • 크기별 시험 중 64MB, 600MB, 3GB 로그는 남아 있지 않고 12GB 로그만 보존됐음
  • 12GB 실행은 약 5.10GiB에서 중단됐으므로 전체 12GB가 끝까지 업로드된다고 단정할 수 없음
  • 모든 계정 등급과 구성 조합을 시험하지 않았음
    • 무료 등급에서 다중 GiB 업로드가 성공했음
    • SuperGrok에서는 “Improve the model”을 끈 상태에서도 git bundle 업로드가 성공했음
    • 테스트에서 업로드를 끄는 설정을 찾지 못했지만 어떤 환경에서도 절대 비활성화할 수 없다고 단정하지 않음
  • 초기에는 PID 단위 nettop 결과를 근거로 대용량 blob이 업로드되지 않았다고 잘못 판단했으나 이를 철회함
    • 별도 업로드 조정 프로세스와 Google IP로 직접 향하는 사전 서명 PUT은 API 호스트나 단일 PID 기준 측정에서 빠질 수 있었음
    • 이후의 프록시 와이어 캡처가 초기 판단을 대체함
  • 결과는 grok 0.2.93, macOS Apple Silicon, 2026년 7월 환경에 한정되며 xAI가 이후 동작을 변경할 수 있음

보존된 주요 증거

  • secrets_responses_body.bin: .env 원문이 /v1/responses 본문에 포함됐음을 보여줌
  • secrets_session_state.tar.gz: 같은 비밀값이 /v1/storage용 아카이브에 들어 있음을 보여줌
  • wire_12gb.log: 5.10GiB 저장 업로드, 83개 /v1/storage* HTTP 200, 저장 실패 0건과 두 채널의 약 27,800배 용량 차이를 기록함
  • model_limit.txt: 모델 요청에서 발생한 402 1회와 429 3회를 기록함
  • crate_strings.txt: xai-data-collector, grok-code-session-traces, storage.googleapis.com 문자열을 보존함
  • uploaded_repo.bundle: 업로드된 git bundle에서 읽지 않은 파일과 전체 Git 이력을 복구한 첫 번째 저장소 증거임
  • uploaded_repo_auth.bundle: 두 번째 독립 저장소에서 같은 결과를 재현한 증거임
  • staged_base_tree_manifest.json: 읽지 않은 파일이 저장소 스냅샷 manifest에 열거됐음을 보여줌
  • staged_metadata.json: 파일 목적지가 gs://grok-code-session-traces/…임을 보여줌
  • gcs_puts.txt는 직접 GCS PUT을 보존하지 못해 비어 있는 placeholder이며, 해당 PUT의 보존 증거로 사용할 수 없음
Read Entire Article