The time I shipped my API key in page source
Before Beacon shipped, I caught a security mistake on my own link-in-bio. A Hardcover bearer token grants account deletion. Mine was visible in view-source for a while.
Before Beacon was Beacon, it was my personal link-in-bio. A static site I'd been hand-building for myself, one of those hobby projects you keep tweaking instead of finishing real work. It had a books tile that pulled my Hardcover library and showed what I was reading.
The tile worked by handing my Hardcover bearer token to the JavaScript that ran in a visitor's tab. The token was on the page as a data-token attribute. Anyone who viewed source could read it.
A Hardcover bearer token grants destructive write access to its account. Including, it turns out, account deletion.
I caught it on a routine view-source pass and saw data-token="..." sitting there. Nothing happened — my reading list isn't a target. But the structural problem was real and Beacon was being built off the same code, which meant I was about to ship that pattern to other people.
The fix is build-time snapshot mode. The Beacon builder fetches the user's current book at save time, stores the result in local state, and the deployed page renders that snapshot directly. The token stays on the user's machine and never reaches the deployed HTML. The tradeoff is that the tile is no longer live; to refresh, the creator opens the builder and clicks Refresh snapshot. Reading progress changes slowly enough that most won't notice.
Two things I'm changing about how I work, going forward.
First, "this credential allows X" is a question I now ask before any tile ships. Hardcover's docs called the token a bearer token; they did not lead with "this can also delete the account." Both are true.
Second, static-site architecture has a tempting shape where every credential the tile fetches with ends up in the page. The fix is to ask, for each credential: does this need to fetch live? Could it be captured at build time and frozen into the page? For anything that updates slower than visitor traffic, the answer is yes, and the credential never needs to ship.
Steam was the next one. Same pattern, same fix.
If you're running anything similar (a static page that fetches authenticated APIs from the browser), view-source on your own page and search for "token," "bearer," "key." If you find one, you have the same job I had.