Privacy on this site runs at three layers. The page arrived last.

In June, P8-10 closed. The newsletter form stopped returning “soon” and started accepting real signups. Four success/failure static pages landed at their canonical URLs. Privacy rendered at /privacy/. That was the moment the site acquired something recognisable as a privacy policy.

It’s also the least interesting of the three layers the site runs for privacy.

The page

/privacy/ exists primarily to be pointed at. Newsletter signups require a link to one. Any future platform requirement or data partnership will reference it. It discloses what the site collects, how that data is used, and how users can request removal. It’s accurate and complete.

But it’s a disclosure mechanism, not a prevention mechanism. Adding it changed what users could read about the site’s data practices. It didn’t change what the site was publishing.

The hook

A month before the privacy page existed, a reader following the macOS launchd TCC article could have parsed the live site and extracted my macOS username. Not from the prose. From the plist example in the code block. An absolute path, copied from the working machine, containing the real username, rendered to production.

In PR #17 I found four lines across two files: the inline plist in src/content/posts/macos-launchd-tcc.mdx, and three absolute-path strings in the scripts directory. The fix was <your-username> in the template. The plist still reads as a valid install guide. The identifier is gone.

The structural fix was a pre-commit hook. It scans every staged file heading to the public repo for the macOS username (case-insensitive) and blocks the commit if it finds one. The bypass token is BBBRAIN_NAMELEAK_BYPASS=1, logged to stderr when used, not silent.

The hook runs globally. Installed once in ~/.git-hooks/pre-commit and inherited across every repo using the shared hooks config. Captainrandom didn’t opt into it specially; the protection is structural, not repo-specific.

What it’s protecting against is a specific failure mode: working content migrating into published content without the machine-specific identifiers being stripped. Config examples, plist templates, absolute paths with real directory structures. All technically correct. All potentially identifying. The failure looks exactly like correct content on review. The hook catches what the eye doesn’t.

The editorial rule

In July, while planning the /work/ expansion, I wrote down what had been running as an operating assumption: Captain Random and the software business are two separate entities. The site must not link them directly.

Projects get discussed architecture-first. A post can describe a project’s stack, its constraints, what decisions turned out to be wrong. What it can’t do is name the business or the client. That isn’t an omission. It’s the intended scope. The personal site documents what I’m learning and building. Clients didn’t sign up to have their projects indexed against my build log.

No hook enforces this. The pre-commit hook fires on string patterns; it can’t catch a post that names a client accurately, in full, and passes the pattern check cleanly. The editorial rule runs before the content is written, not before it’s committed. Different intervention point. Different failure mode addressed.

Three threat models

The three layers are doing different things.

The /privacy/ page addresses legal and platform obligations. It’s a disclosure surface. Without it, signups go out to users who have no documented statement of what happens to their data.

The pre-commit hook addresses accidental disclosure. The specific failure it prevents is working context leaking into published content without identifier scrubbing. That isn’t carelessness in the usual sense. It’s a category error: treating an internal file and a published file as interchangeable because they share a directory and a build step. The hook enforces the distinction structurally.

The editorial rule addresses intentional but scoped content. It’s not catching an error. It’s defining what the correct scope is. A post can be accurate, verified, and outside scope simultaneously. The rule is what closes that gap.

Three layers, three origins. The hook arrived in May after a live incident. The page arrived in June when newsletter signups required one. The rule was written in July when a new feature expansion forced the assumption into words.

The order

Improving on an earlier piece on this site that made the point that the real privacy work extends beyond the page: the three-layer picture is more complete. There’s a third layer that framing didn’t reach, and it has a different character from the hook. Conceptual rather than structural. Editorial rather than automated.

The sequence matters. The hook predated the page by a month, and the rule came after both in July. Technical enforcement appeared after a failure taught what needed enforcing. The compliance surface appeared when a feature required it. The conceptual constraint was articulated when a new feature pushed against its boundary.

That’s probably how this kind of thing accumulates on a site built incrementally. The layers don’t arrive together as a designed system. Each one arrives in response to a different pressure. The design is retrospective.

None of the layers is sufficient on its own. Remove any one and a gap opens:

  • Page without hook: the next plist example ships with the username in it.
  • Hook without rule: a post naming a client project passes the pattern check. The hook never fires. The scope violation lands.
  • Rule without page: signups go out to users with no statement of what happens to their data.

The layers don’t overlap. Each one covers surface the others don’t.

What’s still unfinished

Out of scope: third-party scripts. The site uses external services that have their own data practices; the privacy page references them but nothing enforces limits on them. That gap exists and is acknowledged.

Also out of scope: the /work/ expansion itself. The editorial rule was articulated in the context of planning it. Discussing client work architecture-first, without naming the client, is the principle in the abstract. The expansion hasn’t shipped yet. When it does, the principle will encounter its first real test: whether architecture-first coverage actually holds when there’s specific, named work worth documenting. The abstract statement and the practice may turn out to have edge cases.

When the expansion ships, the rule will either hold or it won’t. The build log will say which.

All writing →