[KongchangAI]
· 4 min read· 2,042 words

A Simple "Reply OK" Test Exposes Grok Build CLI Secretly Uploading Entire Git History

A Simple "Reply OK" Test Exposes Grok Build CLI Secretly Uploading Entire Git History

Grok Build CLI uploaded full git bundles to xAI storage while claiming not to read files, exposing a structural agent blind spot.

Researchers captured xAI's Grok Build CLI 0.2.93 with mitmproxy and found it uploaded complete git bundles — 4 commits, 47 files, full history — to xAI storage even when only told to reply "OK" without reading files. A 12GB repo test showed ~192KB via the model channel and ~5.10GiB via the storage channel in 73 chunks. The privacy toggle only governs training; deny rules can't stop the client's network code. xAI disabled uploads server-side on July 13, but the code remains.

Just reply "OK," don't read any files — this should be the simplest self-evident test for any terminal AI coding agent. Researcher cereblab sent exactly that to xAI's Grok Build CLI 0.2.93, with mitmproxy intercepting traffic in the middle. The model complied. But in the background, the CLI simultaneously pushed a complete git bundle to an xAI storage endpoint: 4 commits, 47 files, the full repository history. Cloning the intercepted bundle revealed the very file it had been explicitly forbidden to open — the marker string fully intact.

Test illustration: two network channels behind a single CLI

This finding matters not because xAI did something bizarre, but because it puts a structural blind spot of AI coding agents front and center: you think you're controlling what the agent can read, but the thing actually uploading your data is the CLI's own network code.

What the packet capture revealed

The second test was even more damning. cereblab prepared a repository with roughly 12GB of random files, and the agent never read any of it. The numbers made the gap painfully clear: about 192KB went to the model channel, while roughly 5.10GiB went to the storage channel — split into 73 chunks of about 75MB each, every one returning HTTP 200, until the capture was stopped mid-run.

A single capture exposed two parallel channels emanating from the same binary. Model dialogue went to cli-chat-proxy.grok.com/v1/responses; storage went to /v1/storage on the same host, plus multipart initialization and batch_exists checks. In a 3GB run, cereblab also saw chunks heading straight to a Google Cloud Storage bucket named grok-code-session-traces. Mixpanel and grok.com event calls hitched a ride as well.

The most unsettling detail was how meaningless the privacy toggle turned out to be. After turning off "Improve the model," git bundle uploads still succeeded with 200s, and the server's /v1/settings response still carried "trace_upload_enabled": true. cereblab's interpretation: that toggle governs training, not uploading.

Secret data slipped through both channels. In a real task where the agent actually read files, a git-tracked .env file containing fake canary values went straight into the model conversation turn, verbatim; the same values also landed in a session_state archive destined for storage.

git bundle is a built-in Git packaging format that serializes a repository's complete object graph — commits, trees, blobs, refs — into a single file, essentially compressing the whole repo into an archive that can be cloned offline. The file begins with a magic string like # v2 git bundle or # v3 git bundle, making it easy to fingerprint in network traffic.

Its danger lies in the word "complete": a single bundle upload doesn't carry just the handful of files visible in your current working directory — it carries the repository's entire history, including everything ever committed and later deleted. So if a secret, token, or internal address ever appeared in any commit, it lives on that history chain forever and leaves the machine with every bundle export. Deleting files and rewriting the working tree doesn't help; the only remedy is to invalidate the secret and rotate it. This is why, when discussing data exfiltration, "files that are tracked" and "files that exist on disk" are two entirely different sets.

July 13: A switch quietly flipped server-side

The same 0.2.93 binary suddenly stopped making storage requests on July 13. cereblab retested six times, zero /v1/storage uploads; the server now returns disable_codebase_upload: true and trace_upload_enabled: false.

xAI responded on X. According to The Hacker News, xAI stated that enterprise zero-data-retention teams had never stored code or trace data, and that consumers could run /privacy in the CLI to disable retention and delete synced data. Musk said previously uploaded data would be "thoroughly and completely deleted." But after actually testing /privacy, cereblab concluded: it controls retention, not what gets sent.

On July 15, xAI open-sourced Grok Build's source code (for first-party code) under Apache 2.0. Tech Times reported that the published source still retains the Cloud Storage upload code. Simon Willison also found "the corpse of the code": upload_session_state() now hard-codes a session_state_upload_unavailable error. The upload path is shut off, but it's still sitting right there in the code.

That's the crux of the problem: a server-issued switch can be flipped back by the server at any time — no new binary appears on your machine, no changelog entry records anything. As of The Hacker News's report, xAI had not specified whether the change covers all accounts, whether it's permanent, how long uploaded data is retained, or how many users were affected.

A server-side feature flag means that client behavior isn't determined by local configuration but controlled in real time by response fields returned from the server. The CLI queries an endpoint like /v1/settings at startup or during operation, and only after receiving values like disable_codebase_upload: true or trace_upload_enabled: false does it decide whether to upload. The governance implications of this pattern are critical: where bytes flow can be changed without updating any binary on your machine or leaving a trace in any changelog. An upload path shut off today can be reopened tomorrow by flipping a single response field.

So when security-assessing this class of agent, a single packet capture only tells you what happened "right now" — not what will happen "from now on." Auditing must be repeatable — built into your CLI upgrade process, not treated as a one-off forensic conclusion.

Deny rules stop the agent, not the client

The sharpest detail is buried in cereblab's reproduction repository: deny rules like Read(secret.txt) did prevent the agent from reading the file, but the tracked file still appeared intact in the uploaded bundle.

Every agent CLI has two layers. One is the model's tool calls: read this file, run that command — your approval prompts and deny rules live here. The other is the client's own network code, which executes whatever the model decides, but layer one can't see layer two at all.

Comparison notes also clarified what a bundle actually carries: tracked files plus their history. Gitignored files that were never committed aren't included; files committed and later deleted come along. If a secret has ever touched a single commit, every bundle of that repository will carry it — deleting the file doesn't help; rotate the key.

cereblab also tested three other CLIs on July 13 using the same canary repository. Claude Code 2.1.204 only sent POST api.anthropic.com/v1/messages. Codex sent model dialogue over WebSocket to chatgpt.com, along with telemetry. Gemini 0.38.2, in API-key mode, showed no bundle during idle runs, and hit a 429 quota error during a real task run. Across these captures, none of the three uploaded a repository bundle — but that's a snapshot of four CLIs on a single day, and next week's versions could differ.

A canary is a deliberately planted, globally unique, easily searchable string — often in the form of a fake key or token placed inside a file. Its purpose isn't to prevent leaks but to make leaks detectable: if that string appears in any outbound request body, bundle, or archive, it proves that file's contents left the machine, regardless of whether the agent claims to have read it.

Auditing with canaries works because it makes no assumptions about "paths": it doesn't rely on the vendor's published host list, nor on trusting tool-call logs. Three canaries neatly cover three scenarios — a file explicitly forbidden to read (tests whether deny rules are bypassed), a secret that exists only in git history (tests whether history travels with the bundle), and a gitignored secret never committed (tests whether only tracked content is exported). Marker checks have their limits too: git compresses objects inside a bundle, so compressed content won't match the string in plaintext — in that case, "a bundle that can be successfully cloned" is itself the strongest evidence.

Audit any agent CLI with a proxy and canaries

You don't need to trust anyone's claims, including mine. Follow cereblab's advice: use a throwaway account when possible.

Create a repository with three canaries: a file the prompt explicitly forbids reading, a secret that lives only in history, and a gitignored secret never committed. Then write a mitmproxy addon that counts outbound bytes per host, flags any canary string appearing in request bodies, and saves any request body beginning with # v2 git bundle or # v3 git bundle (bundle files start with this, and the hook uses standard mitmproxy events).

Start the proxy with mitmdump -p 8080 -s count_egress.py; the first launch writes a CA to ~/.mitmproxy/mitmproxy-ca-cert.pem. Then route the CLI through it from another terminal. Claude Code reads NODE_EXTRA_CA_CERTS for extra CAs; cereblab set SSL_CERT_FILE for Grok. Check your CLI's documentation for the exact variables.

Run two prompts against the CLI you're auditing: the idle one ("don't read any files") tells you what leaves when the agent does nothing; the real-task one tells you what leaves when it works. Ctrl-C to stop mitmdump, and the done hook prints totals by host. If bundle files were saved, clone them to verify:

git clone -q bundle-1.bundle recovered
cat recovered/src/_probe/never_read.txt
git -C recovered log --all --oneline -- old.env

How to read the results? The model host must appear — if it doesn't, the CLI ignored the proxy, and a quiet capture proves nothing. Compare against the vendor's own published host list; any host not on it is a question to ask the vendor. NEVERREAD appearing during an idle run means file contents left without being read; HISTORY appearing anywhere means history is traveling; IGNORED appearing on the model host during a real task run means the agent read your .env. A clonable bundle is the strongest evidence you can get.

This approach has blind spots too. Marker checks can only see uncompressed request bodies, and git compresses objects inside bundles, so for uploads like Grok's, the saved bundle and per-host byte totals are the evidence. No canary alert by itself proves nothing. CLIs with certificate pinning will reject the proxy — that's itself a finding. The request hook only counts HTTP bodies; for cases like Codex transmitting model dialogue over WebSocket, you need to add a websocket_message hook. Large uploads may arrive in chunks, with the bundle header only in the first one, and multipart bodies need their trailing boundary stripped before git clone will accept them. A CLI may ignore these variables and only read the OS trust store — in that case, use a throwaway user account to trust the CA system-wide and delete it afterward.

What this actually changed

Grok Build's upload has been shut off by a server-side flag. But the method remains: one proxy, one canary repository, and a small mitmproxy addon can show you exactly what any agent CLI is sending — Grok or otherwise.

The tools themselves haven't gotten worse. What's changed is what we should assume by default. When you hand a codebase to a terminal agent, what the model sees and what you think it sees were always two different things. And now we know the gap between them can be measured in kilobytes versus gigabytes.

The next time you upgrade a CLI, run the canaries again.

One-line Summary

Grok Build CLI uploaded a complete git bundle to xAI storage while claiming not to read files, exposing a structural blind spot in agent clients.

Paragraph Summary

Researchers used mitmproxy to capture xAI's Grok Build CLI 0.2.93 and found that when told only to reply "OK" without reading any files, it still pushed a git bundle containing 4 commits, 47 files, and full history to an xAI storage endpoint — with the forbidden file's marker string fully intact. In a 12GB repository test, about 192KB went through the model channel while roughly 5.10GiB went through the storage channel, split into 73 chunks of about 75MB all returning 200. The privacy toggle only governs training, not uploading; deny rules only apply to the model's tool-call layer and can't stop the client's own network code. xAI disabled the upload via a server-side flag on July 13, open-sourced the code on July 15, but the upload logic remains, and the server can reopen it at any time. The author recommends auditing any agent CLI yourself with a canary repository and mitmproxy.

#Grok Build CLI#xAI data upload#mitmproxy#git bundle#AI coding agent security#canary audit#privacy toggle
Share:

Related articles