The personal website for Brandon Steili. A work in progress - both the site and the human.

← Writing

Essay

osshp - I Built Analytics That Can't Track You

Published2026·07·11
Filed underosshp
osshp icon

I wanted to know, in the loosest possible sense, whether anyone reads what I post here. That's a reasonable thing to want. What I didn't want was the usual price of wanting that: a third-party script running in every visitor's browser, a cookie banner apologizing for it, and my readers' browsing behavior sitting on somebody else's server for them to do whatever they do with it. So osshp's analytics module is built to answer "is anyone reading this" without being able to answer almost anything else about who they are.

No script, no cookies, nothing to consent to

The entire capture path is server-side. When a page renders, the server records that a pageview happened. There is no client-side analytics script shipped to the browser at all, which means it works identically whether a visitor has JavaScript enabled or not, and it adds nothing to the Content Security Policy: no third-party script origin to allow-list, no inline snippet to worry about, nothing. Most analytics tools require you to either weaken your CSP to let their script run or accept that the CSP and the analytics vendor are now in a permanent low-grade disagreement. osshp's analytics never has that conversation, because there's no script to load.

There are also no cookies. Nothing is set in the visitor's browser, and nothing is read back from it on a later visit. That's not a workaround, it's a consequence of not needing persistent client-side state to do what this module does. It also means there's no cookie banner, because there's genuinely nothing to disclose or ask consent for on that front. Privacy law in a lot of jurisdictions requires a consent banner specifically because a cookie-based tracker is, functionally, tracking. A system with no cookies and no cross-visit identifier isn't dodging the requirement; it just doesn't trigger it.

It honors Do Not Track and Global Privacy Control by not recording those requests at all

If a request arrives with Do Not Track or Global Privacy Control set, osshp doesn't record a pageview for it, full stop. This is worth being specific about, because a lot of sites that claim to "respect DNT" actually mean "we still log the visit, we just don't personalize ads for it." That's not what happens here. A DNT or GPC signal means that visit contributes nothing to the numbers at all. I built it that way because the alternative, quietly logging a visit while telling yourself you're still "respecting" a privacy preference, is exactly the kind of technically-true-but-actually-not behavior that gives analytics a bad name in the first place.

No PII, ever

The module stores no raw IP address and no user-agent string. Neither of those is ever written to the database in a form that could later be tied back to a specific visitor. This isn't a redaction step applied after the fact; those fields simply never make it into storage in an identifiable form to begin with. If there's ever a data breach, a subpoena, or just a curious admin poking at the database, there's no IP-to-visit mapping sitting there to find, because it was never created.

Unique visitors, without anyone being trackable

Here's the part I think is actually clever, and it's worth walking through in detail because the mechanism is the whole point. To estimate unique visitors (not "how many pageviews," but "roughly how many distinct people"), the system needs some way to tell "the same visitor twice" apart from "two different visitors." The usual way to do that is a persistent identifier: a cookie, a fingerprint, something that survives across visits. osshp does the opposite. Each visit is reduced to a salted hash, and the salt rotates daily and is never persisted anywhere.

Walk through what that actually means. On any given day, the same visitor hitting the site twice produces the same hash both times, so they count as one unique visitor for that day, which is the whole point of a unique-visitor count. But the salt used to produce that hash is thrown away at the end of the day and a new one generated for the next. Since the salt never persisted, there's no way, even in principle, even with full access to the database, to compute whether yesterday's hash and today's hash came from the same visitor. The unlinkability isn't a policy or a promise, it's a mathematical consequence of the salt not existing anymore. Nobody, including me, including anyone who ever gets access to the database, can reconstruct a visitor's history across days, because the information required to do that was deliberately never kept.

That's a genuinely different guarantee than "we don't track you across days" as a stated policy. A policy can change, or be misread, or be violated by a bug. A daily-rotating salt that's never persisted can't be un-thrown-away.

Obvious bots get filtered out of the counts, so the numbers aren't inflated by crawlers and scrapers, and everything is retained for 90 days before it ages out. There's a simple admin dashboard to actually look at the numbers, and the whole module is toggleable like every other module in osshp: turning it off stops future capture immediately, but it doesn't delete the history that's already there. Turn it back on later and your past data is still sitting exactly where you left it.

Compare that to what "analytics" usually means

The default posture for most analytics is a third-party script, a cookie, a cross-site identifier, and your visitors' behavior sitting on a server you don't control and can't audit. That's not a criticism of any specific vendor, it's just what the category looks like by default, because the business model of most analytics providers depends on being able to link visits together across sites and over time. Every design choice above is the direct opposite of that default: no script, no cookie, no persistent identifier, no cross-day linkage, no PII, and no vendor in the loop at all. The result is a number on a dashboard that tells me roughly how many people read a post, and tells nobody, including me, anything more specific than that about any individual visitor.

The same instinct applies to images, not just analytics

There's a smaller, related decision worth mentioning here because it comes from the same place. When a blog post references an external image by URL, osshp doesn't just link to it where it lives. It fetches the image, stores a local copy, and rewrites the post to point at that local copy instead (through a hardened fetcher I wrote about in a different post). The reason isn't just tidiness or resilience against the original host disappearing, though it does both of those things. It's that hotlinking an external image means every single visitor who loads that post sends a request, with their IP address and referer, directly to whatever server is hosting that image. That server now has a log entry, for every reader, saying "someone from this IP read this specific post on this specific site, at this specific time." That's a tracking mechanism, even though nobody involved thinks of it as one, and it happens on essentially every blog that links to external images the ordinary way.

Hosting the image locally means that request never happens. The image loads from the same server the rest of the page came from, and no third party ever finds out that a specific visitor read a specific post. It's a different feature solving a different problem than the analytics module, but the underlying idea is the same one running through all of this: you can build something that measures or serves your site well without quietly handing your visitors' behavior to someone else's server in the process.

— end —