RC RANDOM CHAOS

Git never deleted your secrets.

How ZCode's AI indexer uploads your entire git history, including deleted secrets, to the cloud, and why DLP and EDR never catch the egress.

· 7 min read
Git never deleted your secrets.

ZCode is an AI-native code editor. On project open it builds a semantic index of the workspace to feed model context. The index reads the working directory. It also reads the .git directory. The full commit history, every blob, every packed object, every dangling ref, gets serialized, compressed, and shipped to a cloud embedding backend before the first prompt is typed. No CVE. No advisory. This is documented behavior operating as designed.

That last point is why it defeats the controls built to catch bugs. Vulnerability management assumes a flaw. Detection engineering assumes exploitation. Here there is neither. A signed binary reads local files it holds permission to read and sends them over TLS to a first-party API. Every step is legitimate in isolation. The composite is bulk source-code exfiltration, initiated by the developer, on open.

The mechanism sits in git’s object model. A working tree is one snapshot. The .git/objects store is the entire history. Loose objects live under .git/objects/ as zlib-deflated blobs. Packed history lives in .git/objects/pack/ as delta-compressed packfiles, where each object is a base plus a chain of deltas resolved at read time. Deleted files are not gone. A secret committed once and stripped in the next commit still exists as a reachable blob under the parent, and as a dangling object after a rebase or amend. The reflog holds pointers to those otherwise-orphaned commits for its expiry window. git log on the working tree shows a clean file. git fsck --unreachable and git cat-file -p return the plaintext. git rev-list --all --objects enumerates the whole set. CWE-312, cleartext storage of sensitive information, extended across the full commit DAG. CWE-538, exposure of information through directory listing, applied to the object store itself.

ZCode’s indexer does not draw that distinction. It walks refs, resolves the commit graph, inflates objects, and chunks file contents for embedding. Historical blobs enter the same pipeline as current files. An AWS access key rotated out in 2023 rides the same upload as today’s README. The backend stores the vectors and, in most implementations of this pattern, the source chunks that back them, so the model can return grounded context. Retention is set by the vendor. So is sub-processor access. So is jurisdiction.

The retention detail matters because embeddings alone are not the exposure. A vector is a lossy projection, not directly reversible to source. Retrieval-augmented systems keep the plaintext chunk alongside the vector, because the model needs the actual code returned as context, not its coordinates. That plaintext chunk is what sits in the backend store. Whatever regex or entropy check runs at commit time never touched it, because the chunk was assembled from an inflated historical blob, not from the working tree the developer sees. The scanning that would have caught the secret ran against the wrong copy.

The volume is the part reviewers underestimate. History is not the size of the checkout. A repository with years of churn carries ten to a hundred times the working-tree size in its packfiles, because every past state of every file is retained. First-open of a mature monorepo produces an egress event measured in hundreds of megabytes to a single endpoint. The working tree the security team scoped was the small part.

The egress path is where defenders lose the signal. The upload is gzip-encoded JSON over TLS 1.3 to an allowlisted SaaS endpoint, api.zcode.dev or its equivalent. Certificate pinning is standard in these clients. A DLP proxy set for TLS interception either cannot break the pin or is not inspecting a domain already trusted for developer tooling. Content inspection would not help if it could. The payload is not a file with a recognizable magic header. It is an embedding request carrying chunked, sometimes pre-tokenized, source fragments. Regex DLP signatures for AKIA prefixes, PEM guard lines, or JDBC connection strings do not fire on content that is zlib-inflated, re-chunked, and then gzip-framed inside a JSON body.

Map it to ATT&CK. T1567.002, exfiltration to cloud storage over a web service. T1213, data from information repositories, applied to the local git store rather than a Confluence or SharePoint instance. There is no T1055, no process injection, no persistence primitive, because none is required. The data leaves through the door the developer opened, under the identity the developer holds.

The trust chain is the enabling condition, the same failure the supply-chain incidents keep demonstrating. A binary installed for one function inherits reach far beyond that function. ZCode was installed to complete code and answer questions about the repo. Granting it read access to the working tree is defensible. Read access to the object store, and an egress path to a vendor cloud, was bundled into the same grant with no separate decision. The delta between what the tool needed, current-file context, and what it took, full history, is the exposure. That delta was never enumerated because the install prompt asked for folder access and folder access includes .git.

The class is not theoretical. Secrets in git history is the most reliable finding in any red team engagement. gitleaks and trufflehog exist because the hit rate against historical objects is near total. Cloudflare’s 2023 intrusion traced back to an Okta compromise and stolen session material, and the remediation work included rotating credentials that had been sitting in reachable stores. The difference with ZCode is direction and consent. The developer performs the exfiltration voluntarily, silently, on project open, at scale, across every repository ever opened in the editor. An external actor had to break in to reach that data. Here it is handed over by a trusted binary.

Telemetry reality: little fires. EDR observes zcode.exe, or the Electron helper process, make an outbound connection. Sysmon Event ID 3 logs the network flow. The destination resolves to a known developer SaaS. No alert. No parent-child process anomaly. No LOLBIN invocation. No unsigned module load. The process is the vendor’s signed binary doing precisely what its documentation describes. SIEM correlation tuned for beaconing might notice the periodic re-index traffic, but the jitter and cadence match any cloud IDE. The one durable indicator is volume. A first-open egress spike of tens to hundreds of megabytes to a single endpoint shows in netflow and in proxy byte counts. Almost no team baselines per-host developer egress, so the spike passes as noise.

The blind spot is conceptual, not technical. Endpoint and network controls are built to catch code that should not run and data moving where it should not go. ZCode is authorized code moving data to an authorized destination. The trust decision was made at install, not at runtime. Controls that would block an unknown binary reading .git wave through an allowlisted editor performing the identical read. Nothing in the runtime chain is anomalous because nothing in the runtime chain is an attack.

For operators under the Privacy Act and the SOCI Act, the framing sharpens. Source with embedded infrastructure configuration, internal hostnames, API endpoints, and historical credentials leaving the environment to a third-party cloud, potentially offshore, is a data sovereignty question and a notifiable-breach question, independent of whether an attacker is involved. The disclosure happened. The recipient is a vendor sub-processor. The material includes secrets no longer visible in the working tree and therefore never reviewed before the editor opened the repo.

The residual exposure outlives any setting change. ZCode ships a telemetry toggle. Disabling it may stop product analytics and leave repository indexing fully intact, because indexing is the product, not the telemetry. Even with indexing disabled after the fact, history already uploaded stays uploaded. There is no recall. Every secret ever committed to a repository opened in the editor must be treated as disclosed to a third party and rotated. Not the secrets in the checkout. The secrets in the DAG, reachable by git rev-list --all and git cat-file. Rewriting history with git filter-repo or BFG scrubs them from the local store and changes nothing about the copy on the vendor’s backend.

Detection is possible, but it moves left of runtime. Baseline per-developer-host egress volume and alert on first-connection spikes to AI-IDE endpoints. Log DNS and TLS SNI for those domains and correlate against process. Watch for non-git processes performing bulk reads of .git/objects/pack/ through EDR file-access telemetry or Sysmon file events where available. None of these are signatures for an exploit. They are baselines for authorized software doing something at a scale nobody scoped.

No patch closes this, because nothing is broken. The control that applies is set before the editor touches the repo: what data the tool is permitted to reach, and whether git history, distinct from the working tree, was ever in scope for a third-party cloud. Most tool reviews scoped the files on disk. The .git directory was never in the assessment. That omission is the exposure. Treat the tool’s read permission on the object store as the finding, escalate the credential-rotation scope to the security team that owns those secrets, and assume the history left the building the first time the editor opened the repository.

See also: NordVPN for tunneled traffic when operating outside controlled networks.


#ad Contains an affiliate link.

Share

Keep Reading

Latest on the Wire

Full wire →

New signal daily · RSS

Stay in the loop

New writing delivered when it's ready. No schedule, no spam.