Grok's coding CLI uploaded your whole repo — and your git history.
It packaged the entire repository — including files it was told not to open and secrets deleted from history — into a git bundle and shipped it to Google Cloud Storage. I captured it on the wire and cloned it back. As of 2026-07-13 xAI's server returns disable_codebase_upload: true and it no longer fires. Its new /privacy opt-out changes what xAI retains, not what leaves your machine — see the timeline below.
| Tool | Uploads your whole repo? | |
|---|---|---|
| Claude Code | no — only files it opens | [LOCAL] |
| Codex | no — only files it opens | [LOCAL] |
| Gemini | no — only files it opens | [LOCAL] |
| Grok | whole repo + git history → cloud | [FIXED] |
The investigation
-
Route its traffic through memitmproxy + a locally-trusted CA;
HTTPS_PROXY→ every request Grok makes is logged. -
Tell it to do nothingprompt:
"reply OK, do not open any files."Nothing should leave the machine. -
It uploaded anyway
POST /v1/storage → 200, body magic# v2 git bundle. A whole-repo bundle, on an idle prompt. -
Clone the captured bytes
git cloneof the wire body reconstructed the repo — including the file marked never-read, verbatim. -
Measure the blast radiusfull git history rode along — secrets deleted months ago were still in the bundle; read
.envwent to/v1/responses. -
Try to opt outturned off "Improve the model" —
/v1/settingsstill returnedtrace_upload_enabled: true; the upload still fired. -
Prove it's not a flukereproduced on a second, unrelated codebase; other researchers confirmed on their own private repos (269 blobs · 219 MB).
-
The walk-back2026-07-13: same client, but
/v1/settingsnow returnstrace_upload_enabled: false+disable_codebase_upload: true. Upload no longer fires. I can't prove I caused it — only the timeline. -
The fix arrivesxAI ships a
/privacy opt-outcommand and defaults the codebase upload off. Retested on grok0.2.99(binary SHA01bcacec…). -
I tested the opt-outA/B on the wire: opt-in vs opt-out send the same requests — session traces and model turns leave either way. The only change is
/v1/tracesreturning200→204. -
Retention, not transmission
/privacysendsPUT {"codingDataRetentionOptOut": true}— a server-side "don't keep it," not a block on what's sent. Your data still leaves the machine; the upload code still ships in the binary.
What actually stopped the upload was a silent global flag — disable_codebase_upload: true — that applies whether you opt in or out. /privacy is a per-session retention toggle, not the switch that fixed this, so it shouldn't be pointed to as the control. And no developer should have to run an opt-out after every session to keep their own code off someone else's servers. The right default is off.



















