Grok Build CLI Uploads Your Entire Repository to xAI — .env Secrets, Unread Files, and Git History Included
A reproducible wire-level analysis of xAI’s Grok Build CLI (version 0.2.93) documents three upload behaviours active by default: secrets files transmitted unredacted to model endpoints, a whole-repository git bundle sent to a Google Cloud Storage bucket without a size cap, and third-party telemetry to Mixpanel. None of the three are stopped by the tool’s opt-out settings.
The analysis was conducted on macOS Apple Silicon using mitmproxy with a canary repository containing uniquely marked files, including a .env file with dummy credentials. All upload events were confirmed by HTTP 200 responses in wire-captured traffic. SHA-256 hashes of captured artifacts were published alongside the analysis.
Channel A: Secrets Files Go to the Model Endpoint
When Grok reads a file during a coding task, its contents are serialised verbatim into the POST /v1/responses model-turn body. A .env file containing two canary credentials — API_KEY=CANARY7F3A9-SECRET and DB_PASSWORD=CANARY7F3A9-DBPASS — appeared unredacted in the captured 48,070-byte request body alongside "model":"grok-4.5". All six file markers in the test repository were recoverable from the decrypted /v1/responses bodies.
This is unsurprising in isolation: a cloud coding agent must send file contents to its server to act on them. The problem is there is no redaction layer. A .env file is transmitted the same way as a README.md.
Channel B: The Whole Repo Goes to GCS, Including Files the Agent Was Told Not to Open
Independently of what the agent reads, Grok packages the entire workspace and uploads it via POST /v1/storage to a bucket named grok-code-session-traces. This is a separate mechanism from the model-turn channel.
The definitive proof: with the explicit prompt “Reply with exactly: OK. Do not read or open any files,” Grok still uploaded the entire repository as a git bundle (POST cli-chat-proxy.grok.com/v1/storage → 200). Running git clone on the captured bundle recovered a file the agent was told not to open — src/_probe/never_read_canary.txt with its unique marker — plus full commit history. The test was replicated on a second, unrelated codebase with the same result.
The byte split is the load-bearing proof. In the same captured 12 GB session — a repository of 100% random files the agent never read — the two channels moved:
- Channel A
/v1/responses(model turns): 196,705 bytes (192 KB total, 5 requests) - Channel B
/v1/storage: 5,476,228,005 bytes (5.10 GiB)
A ratio of ~27,800×. The model demonstrably could not have ingested the files; 192 KB cannot carry 5 GiB of content. Yet 5.10 GiB left via /v1/storage, with zero failed requests.
The upload scales without a known ceiling:
| Repo size | Upload method | Outcome |
|---|---|---|
| 64 MB | Single POST, 48 MB body | HTTP 200 |
| ~600 MB | ~7.5 MB chunks | All 200 |
| ~3 GB | 50 MB multipart directly to storage.googleapis.com | All 200 |
| 12 GB | 75 MB chunks; 5.10 GiB captured before run was stopped | All 200, 0 failures |
Upload activity continued returning 200s after the model endpoint hit a quota limit (402/429). The codebase upload runs independently of whether the model is answering.
The destination bucket, grok-code-session-traces, is named verbatim in binary strings: crates/codegen/xai-data-collector/src/gcs.rs, crates/codegen/xai-grok-shell/src/upload/{gcs,turn,trace,manifest}.rs. A captured metadata.json gives each file a destination path of the form gs://grok-code-session-traces/repo_changes_dedup/v2/supplemental/sha256_….
The Opt-Out Does Not Stop the Upload
After disabling “Improve the model” in Grok settings, the whole-repo upload continued identically. The server’s /v1/settings response to the CLI returned:
"trace_upload_enabled": true
"upload_enabled": true
"session_registry_enabled": true
"max_upload_file_bytes": 1073741824
The 1 GiB per-file cap in that response suggests individual file uploads are bounded, but the whole-repo bundle mechanism operated without hitting it in testing. The opt-out appears to govern training data policy, not whether code leaves the machine.
Third-Party Telemetry
POST requests to api.mixpanel.com/track, api.mixpanel.com/engage, and grok.com/_data/v1/events were captured, all returning 200.
What This Does Not Prove
The analysis explicitly does not prove xAI trains on uploaded repository contents. Transmission and persistence to GCS ≠ training. That is a separate policy and account-tier question. The analysis also does not test .gitignored files (the upload mechanism is read-driven per the file_access_tracker crate, but .gitignore interaction was not specifically tested) or high-entropy token detection (whether xAI’s backend redacts real sk-...-format API keys was not confirmed).
The Practical Exposure
Any developer running Grok Build against a repository containing a .env file, API keys in config files, or internal source code in git history should treat that content as having been transmitted to xAI’s infrastructure and written to grok-code-session-traces. The Grok privacy settings page offers a prospective opt-out from training use. It does not stop the upload pipeline.
The analysis also flags a reliability issue independent of privacy: the ~/.grok/upload_queue directory stages multi-gigabyte snapshots per session turn and can exhaust disk space under sustained use.
xAI had not issued a public response at the time of publication.