Momentary Authorization, Applied at Home

Back in February I wrote about why authorization should be momentary instead of permanent, Authorization Is Momentary, and the short version of that argument is that most systems only need to answer a narrow question in the moment, is this allowed right now, and then they go and keep the answer around forever anyway, as if the identity itself were the valuable thing instead of the decision.

I did not expect to prove that argument on my own home server a few months later, but that is basically what happened.

For a while I had been hand building API tokens for the home server whenever some script needed to talk to Cloudflare, one to edit DNS, another to manage access policies, and every time a script needed one more permission the easy move was to widen an existing token rather than make a new narrow one, since narrow tokens are more work up front. After a few months of that I had a handful of long lived tokens sitting in files, each one wider than any single job actually needed, and honestly I had lost track of exactly what some of them could do.

That is the same shape of problem as the identity retention argument, just at the machine level instead of the person level, and a standing credential is stored trust in the same way. It sits there being valid whether or not anything is using it, and it gets wider over time because widening is easier than re-scoping. The fix is the same fix too, stop keeping the credential around and instead issue a small proof of authorization for exactly the moment it is needed.

What we built

So we built a small local service, I have been calling it the token broker, that mints a short lived, narrowly scoped token per job instead of handing scripts a standing one, kind of like a hotel key card machine instead of a master key. The front desk holds one master credential and nobody else ever touches it, and every guest gets a card cut for one door, for one stay, that stops working at checkout.

A few pieces make that actually hold up, starting with a policy file listing exactly which scope and resource pairs are allowed to be minted, so a request outside that list gets refused rather than granted on the fly. On top of that policy sits a hard denylist, things like token management itself, billing, membership, and admin access, that no edit to the policy file can override. A disallowed scope does not get widened to the nearest thing that works, it gets refused instead, and the request routes to me as a one time decision, if I approve it, it becomes a permanent, narrow line in the policy file, and if I do not, nothing happens.

The master credential itself lives somewhere only the broker's own system account can read, so no agent, including the ones I work with day to day, can see it, and every mint and every refusal gets written to an audit log by id, never by the token value itself, so the log is useful for accounting and not worth stealing.

It also fails closed, if the master credential is missing, the policy file is broken, or the broker is simply turned off, the answer to every request is no tokens at all. It seems like the obvious choice, but it is easy to build a system that falls back to something more permissive when a piece of it breaks, and I wanted the opposite of that.

The cutover

Once it was working we moved every script and service on the home server that talks to Cloudflare over to minting a fresh token per run instead of reading a standing one from a file, and we kept the standing token wired up as a fallback for each of them, so if the broker itself ever goes down, things degrade to the old behavior instead of breaking outright. As far as I can tell that is the right tradeoff, a broker outage should be an inconvenience and not an outage of everything else. Once the old standing tokens had gone quiet for a while with nothing using them, we revoked the ones that were no longer needed.

Applying it to myself

I wrote the identity essay as a general argument about how systems should treat authorization, and I did not build it with my own home server in mind, it turned out to apply directly anyway. Well, I probably should have seen that coming, but I did not until the tokens were already a mess. A hand built, ever widening API token is exactly the kind of durable trust object that essay is arguing against, it just showed up in a different form. You mint what you need for the moment, throw it away, and the master key never sits anywhere an agent could read it.

The whole thing, the broker, the policy file, the denylist, the audit log, and the cutover of every consumer on the box, got built in an afternoon working with Claude, which I did not expect, a security posture change usually feels like it should take longer than that. Once the shape of the fix is clear the code itself is not really the hard part. The hard part was noticing that the tokens had been getting wider for months and deciding that was actually worth fixing. Honestly it had been sitting there long enough that I had stopped seeing it as a problem at all, more just how things were.