코딩 에이전트가 실제로 업로드하는 것
- 모든 코딩 에이전트가 가진 두 개의 이그레스 채널을 구분하고, 왜 그중 하나만 당신에게 보이는지 이해한다
- Grok Build 와이어 캡처를 읽는다: 같은 세션에서 나온 196 KB의 모델 트래픽 대 5.10 GiB의 저장소 업로드
- 권한 거부와 '내 데이터로 학습하지 마세요' 토글이 모두 지켜지고 있는데도 저장소가 여전히 머신을 떠날 수 있는 이유를 배운다
- mitmproxy와 카나리 저장소로 당신 자신의 에이전트를 도청하고, 엔드포인트별 바이트 수를 직접 센다
- Claude Code, Cursor, Gemini CLI, Codex가 각각 무엇을 전송한다고 문서화하는지 — 그리고 문서가 끝나는 지점이 어디인지 비교한다
당신은 거의 틀림없이 코딩 에이전트의 프라이버시를 이렇게 모델링한다: 읽은 것을 전송한다. 파일 읽기를 승인하면 그 파일이 모델로 간다. 거부하면 가지 않는다. 당신의 프롬프트와 컨텍스트에 있는 파일이 페이로드이고, 나머지는 전부 메타데이터라는 것이다.
그 모델은 출시된 에이전트 중 최소 하나에 대해 틀렸고, 여러 다른 에이전트에 대해서는 구조적으로 불완전하다. 2026년 7월, 한 연구자가 xAI의 Grok Build CLI에 프록시를 걸어보고, 12 GB 저장소가 git 번들로 — 전체 히스토리, 한 번도 읽지 않은 파일, .env 내용까지 — 머신을 떠나는 것을 발견했다. 이는 에이전트가 무엇을 읽었는지와는 전혀 무관한 채널을 통해서였고, 한편 사용자가 이를 멈추려고 찾아갈 만한 설정은 완전히 다른 것을 관장하고 있었다.
이 페이지는 사실 Grok에 관한 것이 아니다. 그것이 드러낸 채널에 관한 것이며, 그 채널은 대부분의 에이전트가 어떤 형태로든 가지고 있다. 그리고 마케팅 페이지를 믿는 대신 당신 자신의 에이전트를 하루 오후 만에 확인할 수 있다는 사실에 관한 것이다.
두 개의 채널
호스팅된 모델과 통신하는 모든 에이전트 CLI는 채널 A를 가진다. 일부는 채널 B도 가진다.
채널 A — 모델 턴. 당신의 프롬프트, 시스템 프롬프트, 도구 정의, 그리고 에이전트가 실제로 읽은 파일의 내용. 이것이 당신의 직관이 추적하는 채널이다. 권한 프롬프트가 관장하는 채널, .gitignore 스타일 거부 규칙이 영향을 미치는 채널, 그리고 트랜스크립트로부터 대략 예측할 수 있는 채널이다. 또한 이 채널은 작다: 작업 세션은 킬로바이트에서 낮은 메가바이트 정도를 이동시킨다.
채널 B — 앰비언트 채널. 클라이언트가 당신의 턴에 답하는 일부로서가 아니라 스스로의 판단으로 업로드하는 모든 것: 코드베이스 인덱싱, 세션 트레이스, 크래시 리포트, 사용량 분석, 피드백 번들. 이것은 모델의 추론이 아니라 클라이언트의 스케줄에 따라 돌아간다. 당신의 트랜스크립트에 없다. 묻지 않는다. 그리고 LLM의 결정이 아니라 코드 경로에 의해 구동되기 때문에, 어떤 프롬프팅도 이에 영향을 주지 못한다 — 에이전트에게 "어떤 파일도 읽지 마"라고 말하는 것은 채널 A를 제약할 뿐 채널 B에 대해서는 아무것도 말하지 않는다.
채널 B가 본질적으로 사악한 것은 아니다. Cursor의 서버 측 인덱스는 채널 B이며 상세히 문서화되어 있다. 위험은 그 존재 자체가 아니라 — 사용자 대면 컨트롤이 거의 항상 채널 A를 위해 설계되어 있어서, 사람들이 출구가 하나만 보이는 상태에서 출구가 둘인 시스템에 대해 추론하게 된다는 점이다.
와이어 캡처
cereblab라는 이름으로 발표한 한 연구자가 Grok Build CLI 0.2.93(macOS arm64, 커밋 f00f96316d4b)을 신뢰된 CA와 함께 127.0.0.1:8080의 mitmproxy를 통해, 심어둔 카나리 문자열이 담긴 일회용 저장소를 대상으로 실행했다. 하네스, 캡처된 증거, SHA-256 체크섬이 재현 저장소로 공개되어 있으므로, 이것은 믿어야 하는 주장이 아니라 당신이 다시 실행해볼 수 있는 주장이다.
캡처가 보여준 것:
두 개의 엔드포인트, 극단적으로 다른 볼륨. 12 GB 저장소를 대상으로 한 단일 세션에서:
| 채널 | 엔드포인트 | 바이트 | 요청 |
|---|---|---|---|
| A — 모델 턴 | POST cli-chat-proxy.grok.com/v1/responses | 196,705 | 5 |
| B — 저장소 업로드 | POST cli-chat-proxy.grok.com/v1/storage | 5,476,228,005 (5.10 GiB) | 82, 전부 HTTP 200 |
대략 27,800배 비율이다. 이 격차가 논증의 전부다: 그 정도 볼륨은 모델이 읽은 것에서 나올 수 없다. 그것은 코드베이스 자체에서 나온 것이다. 업로드는 ~75 MB 청크로 나갔고(로그된 실행에서 73개), 캡처는 그것들이 여전히 성공하는 도중에 잘렸다. 이는 5.10 GiB가 총량이 아니라 하한선임을 의미한다.
한 번도 읽지 않은 카나리. 에이전트에게는 명확한 표현으로 Reply exactly OK, do not read any files라는 프롬프트가 주어졌다. 고유 마커가 담긴 src/_probe/never_read_canary.txt 파일은 어떤 /v1/responses 본문에도 나타나지 않았다 — 채널 A는 지시받은 대로 정확히 동작했다. 그것은 가로챈 /v1/storage 요청에서 복구된 업로드된 git 번들에 나타났다. 연구자는 캡처된 번들을 git clone하여 마커와 전체 히스토리가 온전한 채로 저장소를 되찾았다. 별개의 카나리인 .env 파일의 가짜 DB 비밀번호는 두 채널 모두에서 그대로, 마스킹 없이 나타났다.
목적지는 이름이 붙은 버킷이었다. 바이너리의 문자열과 스테이징된 업로드 메타데이터는 Google Cloud Storage 버킷 grok-code-session-traces를 가리켰고, 객체 경로는 gs://grok-code-session-traces/repo_changes_dedup/v2/… 형태였다. CLI의 설정 자료 어디에도 이것을 설명한 곳은 없었다.
수정, 그리고 대응의 형태. 2026-07-13, 변경되지 않은 바로 그 0.2.93 바이너리가 여섯 번의 재검증에 걸쳐 /v1/storage 요청을 더 이상 발행하지 않았다 — 클라이언트 업데이트가 아니라 서버 측 플래그 전환(disable_codebase_upload: true)이었다. xAI는 공식 권고문이 아니라 X에서 대응했다: 엔터프라이즈 제로 데이터 보존 고객은 영향을 받지 않았다고 했고, 개인 구독자는 /privacy 명령으로 안내되었으며, 이전에 업로드된 데이터는 삭제되었다고 했다. 현재 상태는 지금은-수정됨으로 취급하고, 이 버그의 부류는 영구적인 것으로 취급하라.
소스, 그 이후. 2026-07-14, xAI는 Grok Build 하네스를 Apache 2.0으로 공개했고, 위에서 설명한 기계 장치가 트리 안에 있다: xai-grok-shell/src/upload/gcs.rs, 그 모듈 문서는 업로드 헬퍼를 "the data-collector helpers"라고 부르고, DEDUP_GCS_PREFIX = "repo_changes_dedup" 상수는 위 캡처의 gs://grok-code-session-traces/repo_changes_dedup/v2/… 객체 경로와 일치한다. 와이어 증거와 소스가 일치한다. 공개된 하네스가 그 외에 무엇을 보여주는지는 Inside Grok Build를 보라.
당신이 아마 믿었을, 이것이 무너뜨리는 세 가지
★ "에이전트는 읽은 것만 전송한다." 채널 B가 존재하는 순간 거짓이다. 읽기는 LLM의 결정이고, 인덱싱은 코드 경로다. 둘은 같은 서브시스템이 아니며 게이트를 공유하지 않는다.
★ "파일을 거부했으니 안전하다." 권한 거부는 도구 계층에서 작동한다 — 에이전트가 파일을 컨텍스트로 끌어오는 것을 막는다. 네트워크 이그레스 규칙이 아니다. Grok 사례에서 거부된 파일은 여전히 git 번들 안에 담겨 전송되었다. 위협 모델이 어떤 파일이 머신을 떠나지 않기를 요구한다면, 강제 지점은 에이전트 자신의 권한 프롬프트가 아니라 파일시스템이나 네트워크여야 한다.
★ "내 데이터로 학습하지 마세요" ≠ "내 데이터를 전송하지 마세요." 이것들은 서로 다른 두 개의 컨트롤이며, 벤더는 흔히 첫 번째만 노출한다. Grok의 "모델 개선" 토글은 학습 동의에 매핑되었다; 이를 꺼도 서버는 여전히 trace_upload_enabled: true를 반환했고 저장소 업로드는 변함없이 계속되었다. Anthropic의 문서는 Claude Code에 대해 같은 구분을 명시적으로 그린다 — 학습 데이터 설정과 텔레메트리/피드백 스위치는 별개의 환경 변수를 가진 별개의 손잡이다. 모든 프라이버시 토글을 *"보관해도 되는가?"*에 답하는 것으로 읽고, 결코 *"가져갈 것인가?"*에 답하는 것으로 읽지 마라.
당신 자신의 에이전트를 도청하라
당신은 누구의 문서도 믿을 필요가 없다 — 이 페이지도 포함해서. 15분과 프록시 하나면 당신이 실행하는 어떤 에이전트에 대해서든 확실한 근거를 얻는다. 이것을 가짜 시크릿이 담긴 일회용 저장소에서, 당신 자신의 머신에서 하라.
- mitmproxy는 첫 실행 시 로컬 CA를 생성한다. 에이전트의 TLS 호출이 실패하는 대신 프록시에서 종료되도록 이를 시스템 신뢰 저장소에 설치하라. 이것이 전체 요령이다 — 에이전트 CLI는 평범한 HTTPS 클라이언트이며 대부분 표준 프록시 환경 변수를 존중한다.
- 일회용 git 저장소를 생성하라. .env 파일에 고유 마커(가짜 자격 증명)를 심고, 에이전트에게 절대 열지 말라고 명시적으로 지시할 파일에 두 번째 고유 마커를 심어라. 둘 다 커밋하라 — 커밋된 파일이 git 번들 업로드가 실어 나를 대상이다. 마커는 자연적으로 발생할 수 없는 문자열이어야 하므로, 캡처에 대한 grep이 명확해진다.
- HTTPS_PROXY를 리스너로 향하게 하고 에이전트에게 아무것도 읽을 필요가 없는 태스크를 주어라. 에이전트가 채널 A만 사용한다면, 거의 비어 있는 세션은 거의 아무 바이트도 이동시키지 않아야 한다.
- 호스트와 경로별로 그룹화하여 요청 본문 크기를 합산하라. 이것이 중요한 측정값이다. 아무것도 읽지 않았는데 수백 메가바이트를 이동시키는 세션은, 문서가 뭐라고 하든, 채널 B를 가지고 있다.
- 원시 플로우를 검색하고 — 발견한 아카이브나 번들은 압축을 풀어서 — 두 마커 모두를 찾아라. 한 번도 읽지 않은 마커가 캡처 어디에든 나타난다면 그것은 양성 결과다: 무언가가 모델이 읽은 것과 무관하게 파일을 업로드하고 있는 것이다.
- 이제 학습 동의, 텔레메트리, 분석, 인덱싱을 끄고 — 벤더가 노출하는 모든 것을 — 반복하라. 두 실행 사이의 델타가 그 토글들이 실제로 무엇을 관장하는지 정확히 알려준다. 그것이 토글의 레이블이 좀처럼 답하지 않는 질문이다.
최소 도청 하네스
# 1. proxy + CA (macOS; on Linux use your distro's trust store)
brew install mitmproxy
mitmdump -w flows.mitm --set confdir=~/.mitmproxy &
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain ~/.mitmproxy/mitmproxy-ca-cert.pem
# 2. canary repo
mkdir -p /tmp/canary/src/_probe && cd /tmp/canary && git init
echo 'DB_PASS=CANARY-AAAA-ENVSECRET' > .env
echo 'CANARY-BBBB-NEVERREAD' > src/_probe/never_read.txt
git add -A && git commit -m "canary"
# 3. run the agent through the proxy, asking it to read nothing
HTTPS_PROXY=http://127.0.0.1:8080 HTTP_PROXY=http://127.0.0.1:8080 \
<your-agent-cli> "Reply exactly OK. Do not read any files."
# 4. bytes per endpoint — the number that matters
mitmdump -nr flows.mitm \
--set console_eventlog_verbosity=info \
-s <(echo 'def response(f): print(f.request.pretty_host, f.request.path,
len(f.request.raw_content or b""))')
# 5. did a file you never opened leave the machine?
strings flows.mitm | grep -c 'CANARY-BBBB-NEVERREAD'5단계가 0 이외의 값을 반환한다면, 당신은 채널 B를 찾은 것이다 — 그리고 당신이 스스로 찾은 것이며, 그것이 시간이 지나도 유효한 유일한 종류의 발견이다.
주요 CLI들이 문서화하는 것
문서는 주장이고, 와이어 캡처는 증거다. 둘 사이의 격차가 이 페이지의 교훈 전체다. 그럼에도 주장들은 알아둘 가치가 있을 만큼 다르며 — 그것을 아는 것은 캡처를 실행할 때 무엇을 찾아야 할지 알려준다.
Claude Code. 문서화된 데이터 흐름에는 전체 저장소 인덱싱이나 번들 업로드 채널이 나타나지 않는다; 에이전트는 도구로 검색하고 파일 내용을 모델 턴의 일부로 전송하므로, 떠나는 파일 내용은 컨텍스트로 끌어온 것들이다. 운영 텔레메트리는 별개이며, Anthropic 문서에 따르면 "어떤 코드나 파일 경로도 포함하지 않는다"(DISABLE_TELEMETRY=1); Sentry 오류 리포팅은 또 별개다(DISABLE_ERROR_REPORTING=1). 코드를 실제로 실어 나르는 채널들은 명시적이고 옵트인이다: /feedback는 코드를 포함한 대화 히스토리를 전송하고(DISABLE_FEEDBACK_COMMAND=1), 세션 품질 설문의 트랜스크립트 공유 단계는 당신이 능동적으로 예를 선택할 때만 트랜스크립트를 업로드하며, 알려진 키와 토큰 패턴은 먼저 마스킹된다. CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC는 이 모든 것에 대한 단일 킬 스위치다. 보존 기간: 상용(Team/Enterprise/API)은 30일, 모델 개선을 끈 소비자는 30일, 켠 소비자는 5년; 자격을 갖춘 엔터프라이즈 계정에는 제로 데이터 보존이 존재한다. Anthropic은 조직이 옵트인하지 않는 한 상용 조건으로 전송된 코드로 학습하지 않는다.
Cursor. 교과서적으로 문서화된 채널 B: 코드베이스 인덱스는 저장소를 청크로 나누고, 청크를 임베딩하고, 임베딩과 암호화된 파일 경로를 Cursor의 서버로 전송하여 구축된다. 문서에 따르면 코드 내용은 인덱싱 중 메모리에 보관되었다가 평문으로 저장되는 대신 폐기되며, 청크는 에이전트가 검색할 때 클라이언트 측에서 복호화된다. 이것은 파생된 산물의 실제 전체 저장소 업로드다 — 원시 git 번들과는 실질적으로 다른 위험 프로필이며, Claude Code의 필요한 것만 읽는 모델과도 실질적으로 다르다. 셋 중 어느 것을 원하는지는 판단의 문제다. 어느 것을 실행하고 있는지 모르는 것은 판단의 문제가 아니다.
Gemini CLI. 사용량 통계는 기본으로 켜져 있으며 settings.json의 privacy.usageStatisticsEnabled: false로 비활성화된다; 문서는 프롬프트와 응답 내용, 그리고 읽거나 쓴 파일의 내용은 로깅되지 않는다고 명시한다. OpenTelemetry 계측은 별개이며 당신이 관리하는 컬렉터로 향하게 할 수 있다(telemetry.enabled: false로 끈다).
Codex CLI. 익명 클라이언트 분석은 기본으로 켜져 있으며 분석 설정 플래그로 비활성화된다; OTel 익스포트는 당신 자신의 컬렉터로 라우팅할 수 있고, 프롬프트 로깅(log_user_prompt)은 활성화하지 않는 한 꺼져 있다. 세션 트랜스크립트는 CODEX_HOME 아래에 로컬로 유지된다 — 아래를 보라.
아무도 확인하지 않는 나머지 절반: 당신 자신의 디스크
이그레스는 한쪽 방향일 뿐이다. 이 에이전트들 하나하나가 당신의 세션 — 프롬프트, 파일 내용, 도구 출력, 때로는 시크릿까지 — 을 평문으로 로컬 디스크에 기록하며, 그 파일들은 세션보다 오래 남는다.
Claude Code는 트랜스크립트를 기본적으로 30일 동안 ~/.claude/projects/ 아래에 저장한다(cleanupPeriodDays로 조정). Codex는 히스토리를 CODEX_HOME 아래에 유지한다. Grok Build는 업로드를 ~/.grok/upload_queue에 스테이징했다 — 보고에 따르면 턴당 기가바이트 규모였고, 부하 상태에서는 디스크를 가득 채울 때까지 커질 수 있었는데, 이는 프라이버시 버그 안에 숨어 있는 서비스 거부 버그다.
결과: 노트북 백업, 동기화된 폴더, 또는 당신의 홈 디렉터리에 읽기 권한을 가진 두 번째 프로세스는, 당신이 방금 계측한 네트워크 스택을 결코 건드리지 않는 유출 경로다. 세션에 들어간 모든 시크릿은 이제 당신이 두지 않은 어딘가에 평문으로 디스크에 있다.
실제로 버티는 하드닝
두 스레드에 걸친 수백 개의 댓글 — Hacker News 논의에서 반복된 주제는, 마크다운 수준의 지시는 보안 경계가 아니라는 것이었다. CLAUDE.md와 그 등가물들은 모델에 대한 안내이지 바이너리에 대한 강제가 아니다. 어떤 컨트롤이 클라이언트에 맞서 버텨야 한다면, 그것은 클라이언트 아래에 살아야 한다.
- 작업 저장소만 마운트한 컨테이너나 devcontainer에서 실행하라. 이것이 단일 최대 레버리지 변경이다: ~/.ssh, ~/.aws, 셸 히스토리, 다른 클라이언트의 저장소, 비밀번호 볼트를 볼 수 없는 에이전트는, 문서화되었든 아니든 어떤 채널로도 그것들을 업로드할 수 없다. 별도의 OS 사용자는 더 적은 격식으로 상당 부분 같은 것을 달성한다.
- 에이전트의 컨테이너에서 나가는 트래픽을 기본적으로 거부하고, 필요한 모델 API 호스트만 허용하라. 당신이 승인한 적 없는 채널 B는 닫힌 이그레스 정책을 통해 다이얼 아웃할 수 없다. 덤으로, 이것은 미래의 모든 문서화되지 않은 업로드 채널을 사고가 아니라 당신의 로그에 남는 연결 오류로 전환한다.
- 작업 트리의 .env는 에이전트가 유출할 수 있는 가장 값진 단일 파일이며, 커밋되어 있어서 번들 형태의 업로드가 실어 나를 가능성이 가장 높은 파일이다. 시크릿은 실행 시점에 시크릿 매니저나 환경에서 주입하라; 저장소 밖, 마운트된 트리 밖에 두어라.
- 앞 섹션의 캡처를 모든 프라이버시 설정을 최대로 한 채 한 번 실행하라. 15분이면 주장 대신 사실을 얻는다 — 그리고 이것이 이 사고를 연구자가 하기 전에 잡아냈을 유일한 단계다.
- 무엇이 업로드되었고 무엇이 안 되었는지 재구성하려 하지 마라. SSH 키, 클라우드 자격 증명, API 토큰이 노출 기간 동안 에이전트가 접근할 수 있었던 디렉터리에서 도달 가능했다면, 그것들을 공개된 것으로 취급하고 로테이션하라. 자격 증명 로테이션은 저렴하다; 어떤 바이트가 이동했는지에 대한 포렌식 논쟁은 그렇지 않다.
- 제로 데이터 보존과 상용 조건은 위 컨트롤들의 대체물이 아니지만, 벤더가 무엇을 보관하도록 허용되는지와 당신에게 어떤 구제책이 있는지를 바꾼다. 유출을 감당할 수 없는 코드베이스 위에서 에이전트를 실행하고 있다면, 소비자 등급은 잘못된 등급이다.
Check yourself
0/4이것이 다음에 향하는 곳
이 사고에서 모델에 특정한 것은 아무것도 없었고, 새로운 공격을 요구하지도 않았다. 그것은 그럴듯하게 들리는 무언가 — 에이전트에게 더 나은 컨텍스트를 주기 위해 저장소를 서버 측에 캐싱하는 것 — 를 드러내지 않고 한 클라이언트, 그리고 어휘("모델 개선")가 사용자가 실제로 던지던 질문에 매핑되지 않는 설정 화면을 요구했을 뿐이다.
이 둘 다 극도로 흔하다. 에이전트 CLI 카테고리는 18개월 되었고, 빠르게 움직이며, 코드를 업로드하는 기능을 출시한다. 코드를 업로드하면 제품이 더 나아지기 때문이다. 이런 일은 더 많이 예상하라. 방어 가능한 입장은 가장 좋은 프라이버시 페이지를 가진 벤더를 고르는 것이 아니다; 그것은 측정하는 습관을 들이고, 잘못된 답이 초래할 수 있는 비용을 제한하는 상자 안에서 그것을 실행하는 것이다.
같은 문제의 적대적 측면 — 이미 당신의 권한을 가진 에이전트를 조종하는 신뢰할 수 없는 콘텐츠 — 을 원한다면, 코딩 에이전트가 무기화될 때와 자율 실행 하드닝을 읽어라. 데이터 흐름 이외의 모든 것에서 CLI들이 어떻게 다른지는 코딩 에이전트 CLI 비교를 보라.
출처 및 더 읽을거리
- cereblab — "What xAI Grok Build CLI actually sends to xAI: a wire-level analysis (grok 0.2.93)" — 일차 캡처: 엔드포인트, 바이트 수, 카나리 방법론, 바이너리 해시.
- cereblab/grok-build-exfil-repro — mitmproxy 하네스, 카나리 저장소, 증거 디렉터리. 직접 재현하라.
- Hacker News — "Grok CLI uploaded the whole home directory to GCS" — 400개 이상의 댓글, 대부분 격리 전략에 관한 것.
- Hacker News — "xAI's Grok Build CLI Uploads Git Repositories to a Google Cloud Bucket" — 와이어 수준 분석에 관한 스레드.
- The Hacker News — "Grok Build Uploaded Entire Git Repositories to xAI Storage" — 타임라인과 xAI의 대응.
- Anthropic — Claude Code data usage — 텔레메트리, 피드백, 보존, 그리고 각각을 제어하는 환경 변수.
- Cursor — Codebase indexing — 인덱스가 무엇을 업로드하고 무엇을 저장하지 않는지.
- Gemini CLI — configuration and telemetry —
usageStatisticsEnabled, OTel 라우팅. - mitmproxy — 프라이버시 주장을 측정으로 바꾸는 도구.