PR #17 was four lines across two files.
In the first file: a plist example embedded in a published MDX post. A ProgramArguments entry with an absolute path. The author’s real macOS username in the filesystem path, rendered verbatim in the live article’s HTML. In the second file: three more absolute-path strings of the same shape. No credentials. No tokens. Just the macOS username, shipped with the content because the content was written accurately, and accuracy felt like the point.
The post had been live for a week by the time the production audit caught it. The review cycle hadn’t flagged it. Neither had git diff. The username wasn’t in .env, wasn’t in a config file, wasn’t anywhere a credential scanner would look. It was in the prose.
That’s the gap this article is about.
Where the leak came from
Technical writing lives close to the terminal. The post needed a concrete ProgramArguments block. A concrete block has an absolute path, and an absolute path on macOS starts with /Users/. The example was a real path, used on the author’s real machine, documented accurately. The alternative was a placeholder no one had yet invented a convention for, and that felt imprecise.
That impulse is correct in code. A // TODO: replace with real path in a deployed codebase is a problem. The same impulse in published content produces a different failure: it ships identifiable information as documentation.
The /now/ page had the same problem in a different form. Eight concerns across one content block: private system names implied by the page’s specificity, commitments that referenced internal processes, implementation details that belonged in a private runbook. Nothing as direct as a filesystem path. The same structural issue, though: content written at the level of specificity that made sense for the author’s working context, shipped to a public surface without a translation layer.
The second vector: named repositories
The /learning/ page was subtler. It pulled commit activity, open issues, and progress indicators across active repositories. The names it was surfacing: BBBRAIN, EDITORIAL-STARTER, UBERTUTORS-INSTANCE, MARKET-VISION-CORE, BYTE-BRIDGES. All private. All commercial projects that appear nowhere on the public GitHub profile. The page had been publishing a summary of the internal portfolio.
The fix wasn’t to remove the aggregation. The aggregation was the point of the page. The fix was to change what the aggregation exposed. The /learning/ page now pulls GitHub /languages byte-counts across every repository on the account and aggregates them into technology-level tiers: Python, TypeScript, Bash, Swift. Language distribution, not project names. The reader can see what technologies are in use. They cannot see what projects those technologies are applied to.
That distinction was in the data the whole time. The aggregation layer just had to be aimed at a different level of abstraction.
Why the policy page doesn’t help
The privacy policy at /privacy/ is necessary. It covers data collected through the newsletter signup, cookie behaviour, third-party processors. It’s correct.
It couldn’t have caught the plist path or the repository names. Those aren’t data the site collects from users. They’re data the author committed into a content file. The policy document is a promise made to readers about how their data is handled. It says nothing about the author’s own information leaking through the content pipeline.
Content is code. An MDX file commits, pushes, builds, deploys through the same pipeline that handles component logic. If you wouldn’t hardcode a username into a React component, you shouldn’t hardcode it into a code block in a post. The habit that prevents one doesn’t naturally prevent the other. Technical prose gets written in a different mental mode from code, even when both are compiled by the same tool.
Policy doesn’t close that gap. The pipeline has to.
Fix 1: the placeholder convention
For technical content with absolute paths (plist examples, shell scripts, configuration blocks), the convention now is __USERNAME__ as a sed-friendly placeholder. Scripts meant for installation include a substitution step in the runbook: sed "s/__USERNAME__/$USER/g". The repo carries the template; the machine-specific value never gets committed.
The plist that triggered PR #17 has been updated to this form. Future articles that reference a path under /Users/ use __USERNAME__ in installable templates and an HTML-escaped placeholder (/Users/<your-username>/) in prose. The convention is lightweight enough to apply consistently and self-documenting enough that the reason is obvious on inspection.
Fix 2: the pre-commit hook
The enforcement mechanism is a pre-commit hook in the global hook repository at ~/.git-hooks/. On every commit, it scans every staged file for the literal macOS username, case-insensitive, and for the full legal name as a separate pattern. A match blocks the commit.
The bypass token is BBBRAIN_NAMELEAK_BYPASS=1, available inline or as a shell variable. It logs to stderr. The bypass exists because there are legitimate cases: git history references, internal notes, files that never publish. The friction is the point. The default path is blocked.
Installation on a new machine:
git clone https://github.com/CaptainRandom00/git-hooks.git ~/.git-hooks
git config --global core.hooksPath ~/.git-hooks
Every commit across every project on the machine inherits the check. The hook activates for any repo that has docs/devlog.md — every active commercial project, without per-repo configuration.
What this is actually enforcing
The hook and the aggregation change are the same answer at different layers. Both remove the burden of remembering from the author and put it in the pipeline.
The author who wrote the plist path was being accurate. The code example reflected the actual filesystem. The pre-commit hook would have blocked the commit anyway, and that’s the right outcome. Technical accuracy in a post doesn’t require a real path; it requires a credible one. __USERNAME__ is credible. It communicates the structure without exposing the value.
The /learning/ page now shows which languages are in use, not which projects are active. A reader knows the stack spans Python, TypeScript, and Swift. They don’t know the names of the clients those skills are applied to. The aggregation layer made that distinction automatic.
The order of operations
The privacy policy came last. The hook and the aggregation changes came first. The policy page documents what the site does with reader data. The pipeline changes determine what the author’s own information does or doesn’t ship.
Getting the order backwards (policy before pipeline) is a common pattern in content-heavy projects. The policy is visible and produces a deliverable. The pipeline fix is invisible and produces a constraint. The constraint is harder to motivate until you’ve shipped a plist with a real username in it, found it in a production audit, and opened the pull request that should have been a pre-commit check.
The hook is running now. The next article that needs an absolute path in a code example will use __USERNAME__, and the commit that includes it doesn’t require the author to remember why.



