xAI’s Grok Build coding CLI was uploading entire Git repositories, full commit history and all, to a Google Cloud Storage bucket run by xAI, not just the files a coding task needed.
A researcher publishing as cereblab, testing version 0.2.93, captured one of those uploads, cloned the git bundle out of the intercepted request, and pulled back a file the agent had been told in plain terms not to open.
The upload rode a separate channel from the model itself, and the byte split is hard to argue with. On a 12 GB repo of files the model never read, model-turn traffic to /v1/responses came to about 192 KB while the storage channel to /v1/storage moved 5.10 GiB, a roughly 27,800x gap between what the model needed and what left the machine.
That storage upload ran as 73 chunks of about 75 MB, every one returning HTTP 200, and across the researcher’s size sweep the volume tracked total repo size. The destination bucket, grok-code-session-traces, is named in the binary and in a staged metadata.json whose per-file paths point at gs://grok-code-session-traces/.
The unread file was src/_probe/never_read_canary.txt, planted with a unique marker. Cloning the captured bundle recovered it verbatim along with the repo’s full commit history, and the same test replicated on a second, unrelated repo. What the captures establish is transmission, acceptance, and storage, not training.
The teardown does not claim xAI trained on the code, that staff read it, or that gitignored files are always swept in. Tracked files plus history is what the wire shows.
The secrets path is separate and simpler. When Grok reads a file, its contents go into the model turn, and a tracked .env went with them unredacted, canary API_KEY and DB_PASSWORD values and all. The same content also landed in a session_state archive bound for storage. The planted secrets were fake, so nothing real leaked in the test. The behavior is still the problem: a credential file the agent read during a task went out and was stored with no redaction.
The setting most developers would reach for did nothing here. With “Improve the model” turned off, Grok still uploaded the repository, and the server’s own /v1/settings response kept returning trace_upload_enabled: true. That toggle governs whether your data trains the model. It does not govern whether your code leaves the machine. Those are two different controls, and only one of them was exposed to the user.
Every cloud coding agent has to send some source to a remote model to do its job, so the first channel is expected. Sending the entire tracked repository and its history is a wider boundary than sending the files a task needs.
A repo can hold proprietary code, internal URLs, customer data, and credentials that were removed from the working tree but still sit in commit history. In cereblab’s own cross-tool comparison, Claude Code and Codex sent no repository bundle; Gemini sent none in an idle test, though its realistic-task run was quota-blocked before it finished.
Grok Build was the outlier. Those are still cloud tools that send the files they open, so “local only” is the wrong mental model for any of them. But wholesale collection of the workspace was specific to Grok Build.
Leave a Reply