PR #17 fixed four lines across two files. The lines were in macos-launchd-tcc.mdx, a post already live and rendering on the site, and in the scripts directory. They contained my macOS username in absolute path strings. Not careless in any conventional sense: the paths were illustrative examples, included because they were real. The fix was <your-username> placeholders throughout, a corrected inline plist, one commit. Twenty minutes.
What took longer was the question of what would prevent the next one.
Two kinds of privacy
Improving on the incident-level analysis in I patched the same privacy leak four times, the lesson isn’t to be more careful, which documented the pattern but stopped short of naming where enforcement should live: the answer is build-time, not policy.
On a personal site, “privacy” covers two problems that have almost nothing to do with each other.
The first is user-facing. A newsletter form collects an email address. GDPR applies. ICO registration applies. A policy page is required. This problem is solved by a /privacy route, consent flows, and data processor agreements with your ESP.
The second is developer-facing. Your build pipeline can publish your home directory structure, internal project names, real system handles in place of intended generics, machine-specific paths in code examples. That isn’t a GDPR problem. It’s a publishing accuracy problem. Your own data, not your users’, reaching the CDN in a form you didn’t intend.
The /privacy route shipped on 9 June, as part of closing Phase 8. The newsletter form went live the same day: real signups, real email addresses, real data-processor obligations. The privacy page was the correct response to that. It documents what the form collects, how it’s processed, what users can request.
It has nothing to say about username strings in MDX.
The audit that preceded both
Block 16 of the production audit was /now/. The page listed private project names, internal codenames that assumed insider context, identified things that hadn’t been announced, and meant nothing to an outside reader. It also had a last-updated date hardcoded rather than derived from git.
The fix stripped the private names and replaced them with what the external reader actually needs. The date derivation moved to git log. Neither change was about user data. Both were about the boundary between internal vocabulary and published content.
That boundary is where the developer-facing privacy problem lives. It doesn’t respond to a policy page.
The hook
The pre-commit hook installed with PR #17 blocks any commit whose staged content contains the literal username (case-insensitive) or the full real name. Bypass is available for genuine edge cases, but it requires an explicit token in the commit command, not a toggle you leave on.
The hook doesn’t know about intent. A code example in an MDX article and a per-machine config template look the same to it. For the latter, where an absolute path with a real username is the correct value on the local machine, the convention is a sed-friendly placeholder: __USERNAME__. The install runbook documents the substitution step. The distinction is build-time discipline: sentinel in the repository, real value only after substitution on the target machine.
The policy page doesn’t cover this. The hook enforces it.
The same instinct, further in
The mastery tracking work that shipped in July, Phase 11.34, is the personal layer over the skills hub. It keeps learning data device-local. Completion percentages, session counts, the learner’s own progression: one write, four reads, all on the device. The data is there to tell the learner where they stand. It doesn’t go to the site’s build output.
That’s not a leak-prevention decision in the PR #17 sense. It’s an architectural one: what belongs on the device versus what belongs in the repository and the CDN. But it runs on the same instinct: build-time decisions about what data crosses which boundary, rather than runtime trust that nothing sensitive reached the output.
Sequence
The hook shipped three weeks before the /privacy page. That was the correct order, even though neither was planned against the other.
Reverse the order (policy page first, hook never) and the site would correctly explain how it handles newsletter subscribers’ data while it kept publishing macOS usernames in code examples until someone caught each one manually. The policy page doesn’t prevent that. The hook does.
This isn’t a criticism of policy pages. Sites that collect user data need them, and the legal obligation is real. The point is narrower: a personal site developer who asks “what do I need to do about privacy?” and reaches for a /privacy route first has identified the wrong layer. The first privacy work on a personal site is usually blocking your own data from reaching your own CDN, not documenting what you do with your users’ data. Before the newsletter form goes live, there aren’t any users’ data to document.
Build-time versus policy
A policy page documents an existing practice. It runs after the fact, on the assumption that data will be collected and that users deserve a record of the handling.
A hook prevents a fact from existing. It runs before the commit, before the push, before the deploy. The string never reaches the repository. There’s nothing to document.
For user data, the policy model is often sufficient. GDPR is largely about process, records, and user rights: after-the-fact mechanisms that codify what you do with data once you have it. The page belongs.
For your own data leaking into published content, the policy model runs too late. The string is already in the MDX, already rendered, already indexed. Shipping a corrected version helps, but the window between publish and fix is real. The hook closes it.
Out of scope
This isn’t an argument against privacy policy pages. If you collect user data, you need one. The scope here is narrower: what privacy work to do first on a personal site, and where enforcement should live. The answer is a commit hook, not a route.



