← Blog
✎blogbericht

Publishing Without Publishing Yourself

A dim teal-lit archive of tall metal shelving stacked with file boxes receding into the background, the AMARBARO seed-root-boot mark centered over the aisle.

Reviewing a repo's working tree before making it public checks the wrong artifact: the identity and size problems that matter live in commit metadata and history, which a checkout never shows you.

On 2026-09-19, two repos that had been local-only by design since they started, mojo-baro and its Android counterpart, went public. Before either one's history reached anyone else's disk, a scan of the full commit range found 361 commits carrying a private email address, and separately found 112 MB of committed build output in the Android repo. Neither of those two things is visible if you check out the current tree and read it. That gap between what a checkout shows and what the repo actually contains is the entire subject of this post.

The number that reading the code cannot find

git log --all --format='%ae%n%ce' | sort -u lists every author and committer email that has ever touched a repo, across every commit, not just the ones reachable from the current branch tip. Run against mojo-baro's full history, that command surfaced a Proton address used across 361 commits. Not one of those 361 commits has that address anywhere in a tracked file. A person cloning the repo, checking out main, and reading every line of source would find zero occurrences, because the address was never written into the code. It lives entirely in commit metadata: the author and committer fields git stores alongside every commit object, which git log, git blame, and every hosting platform's commit history view all read directly from the object, independent of what the commit changed.

This is the load-bearing fact of the whole exercise: a working-tree review and a history review check disjoint sets of things. Grepping the checked-out files for a home path, a private key, or a real name catches exactly nothing that lives only in the metadata layer, and the metadata layer is exactly where authorship information accumulates by default, silently, on every single commit, for as long as the repo has existed. A repo that has been local-only for months or years has months or years of that accumulation, none of it visible from the tree.

The fix for mojo-baro's case was a mailmap remapping every one of those 361 commits' author and committer identity to a single public address, applied as one step of a git-filter-repo pass that rewrites history rather than patching the tip. The mailmap and the exact strings it replaces live outside the repo, under ~/Brain/mojo-baro/republish/, because committing the rules that describe what was removed would publish the removed thing a second time, inside the file that documents removing it.

The second kind of leak: what nothing flags because nothing is wrong with it

The Android repo's history separately carried 112 MB of committed build output under llama/.cxx/, the CMake and Gradle intermediate directory for a native build. This is not a secret, not personal information, and not a security problem in any sense a scanner looks for. gitleaks, which caught the false positives worth allowlisting elsewhere in the same pass, has nothing to say about a .cxx/ directory: it is not shaped like a credential, because it is not one. It is just 112 MB of generated intermediate files that somebody, at some point, ran git add . against, and every clone of that repo from then on pays for it, forever, because history does not forget a committed blob just because a later commit deletes the file.

The same publish-purge pass that carries --drop-path for exactly this case removed it, and the tool's own large-blob report is what surfaces it in the first place: it prints the ten largest blobs in history before you write a single purge rule, precisely because nobody reviewing pull requests one at a time ever sees the accumulated weight of what got committed and later deleted. A file that was added in commit 40 and removed in commit 41 is invisible in a diff of commit 42 against commit 41. It is fully present in the repo's total size, and it stays present in every clone until something rewrites history to remove it.

The things worth purging before a repo goes public are, almost by definition, the things a checkout of the current tree cannot show you. If grepping the working directory would have found it, it would already have been fixed.

A third instance, smaller, same shape

The home path itself is the same class of problem at a smaller scale. Shell scripts in both repos referenced /home/mario/ directly, sometimes inside double-quoted strings where a ~ would have expanded and a literal path would not. The purge rule for this is not "replace the home path with ~/," because ~ inside a double-quoted string in a shell script does not expand, and a naive substitution would silently break every script for anyone who clones the repo onto a different username. The rule instead replaces it with the $HOME environment variable, which does expand inside double quotes, in every file and every historical commit that contains the literal string. Getting this rule wrong would not have shown up in any review of the current tree either: the scripts would look plausible, syntactically correct, and would only fail for the next person who ran them from a different home directory, which is to say, everyone who was not the original author.

The rule

Purge rules come from a scan of the actual history, never from memory of what might be in there, and they live outside the repo being purged.

The second half of that rule is not incidental. Writing the replacement rules into a file committed inside the repo they describe means the file itself is the leak: it names, in plain text, every string that was supposedly removed. ~/Brain/mojo-baro/republish/ holds the mailmap and the replace-rules file for this pass, in a vault that is itself local-only, never in the export.

What counts as done here and what does not

What counts: - git log --all --format='%ae%n%ce' | sort -u run and every address on the list accounted for, either as intentionally public or remapped. - A large-blob report read in full, not just skimmed for the top line, because a 100 MB entry buried at position 4 is exactly as real as one at position 1. - publish-purge ending on the literal string PASS -- all verifications clean, with the tip-tree diff read, not just the exit code. - One branch in the export. --keep-branch main, nothing else, because an export that still carries lane branches is one git push --all away from publishing every unfinished idea in the repo.

What does not count: - Reading the current README.md and source tree and concluding the repo is clean, because that review cannot see either of the two things this post is about. - A gitleaks pass with no findings. gitleaks looks for credential-shaped strings; a home path, a real name, and 112 MB of build output are not credential-shaped, and it correctly says nothing about any of them. - Trusting that a .gitignore entry added later means the file it now ignores was never committed. It was; the ignore only stops new commits from adding it again.

What it cost

Two full exports, ~/Projects/mojo/mojo-baro-public-20260919 and ~/Android/baro-public-20260919, each a complete git-filter-repo rewrite of the entire history, neither one pushed anywhere by the tooling itself. Every commit hash in both repos changed, because rewriting author metadata on a commit changes that commit's hash, and every commit after it. That is not a side effect to work around; it is the entire mechanism, and it means an export is a one-way artifact: nobody can force-push their way back to matching the original private history, nor would anyone want to. The working repos kept no origin remote through any of this, on purpose, so that the only way either repo's history reaches a public host at all is a deliberate git remote add and git push that the owner runs by hand, after reading the export's own verification report, not before.

How to prove this wrong

Take any repo of your own that has been local-only for a meaningful stretch of time and run the one command this post opens with: git log --all --format='%ae%n%ce' | sort -u. If every address it prints is one you would be comfortable seeing on a public commit history, the identity half of this post's claim does not apply to your repo, and that would be worth knowing. Separately, run git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | sort -k3 -n -r | head (or use a purpose-built tool's large-blob report) and look at what the top of that list actually is. A repo where both checks come back clean has nothing this post is warning about, and the falsifier for the whole piece is exactly that: an empty result on either check, on a repo old enough to have accumulated the kind of history this post describes.

Provenance

mojo-baro and baro.apk, both local-only from their first commit until 2026-09-19. Tooling: publish-purge at ~/iTools/dev/publish-purge/, the clean-publish skill documenting the procedure, gitleaks for the credential scan. Every figure above traces to the session note at ~/Brain/mojo/mojo-baro/2026-09-19-moepf-lane-and-publish-prep.md, section "Publish prep," and to the exported branches named there. Neither export has been pushed as of this writing; the accurate public sentence for each repo is decided and written before the first push, not after.

Reacties

Nog geen reacties.

Inloggen om te reageren.