This has happened to me enough times that I have stopped being surprised by it. I work out how something should go — a process, an operating model, in this case an application — by reasoning about what the thing actually has to do. I have no formal training in any of it. Then, sometimes months later, I find out that the shape I arrived at has a name, a literature, and people who argue about it at conferences.
This one arrived by way of a failed attempt to impersonate myself.
Running the app in the simulator kept interfering with how passkeys were
retained. The fix seemed obvious: a second account just for the simulator
— call it nigel-sim — while still telling the
application I was my ordinary self, so nothing downstream had to care which one
I had signed in with.
The identity provider does this happily. A property mapping rewrites the
claims it emits, so nigel-sim could authenticate as itself and hand
nigelandre to the application. I set it up, tested it, and it
worked exactly as advertised: every profile claim came back with my ordinary
username. I removed the attribute and it reverted. I put it back and it changed
again. Textbook.
Then the application looked at all of that and said: nigel-sim.
Not failed to read the claim. It read it. It just did not care. And
when the request went on to the host, the host also went looking for
nigel-sim, did not find it, and refused.
Which is when it occurred to me what I had actually just done.
I had attacked myself, badly
Strip out the motive and the configuration reads like this: an identity claim was deliberately rewritten so that one account asserted it was a different account with more authority. That is the whole shape of an impersonation attempt. I had simply arrived at it while trying to stop a simulator from eating my passkeys.
If anything downstream had been quietly reasoning the identity provider
says this is nigelandre, so this is nigelandre,
this was precisely the setup that would have found it.
Nothing was. The chain just kept politely disagreeing with me — its creator:
the identity provider says: username = nigelandre
the application says: principal = nigel-sim
the host says: resolve nigel-sim
PAM says: is nigel-sim valid here?
sudo says: what may nigel-sim execute?
So the workaround was a failure, and the accident was a result: rewriting a username did not hand one account another account’s identity, let alone its privileges.
At which point I got curious
Having established that nigel-sim was genuinely a stranger to
the system, the obvious next question was how far a stranger could get. So I
stopped trying to fix the passkey problem and started handing the account
authority one piece at a time, to see where each request would die.
Authenticated and nothing else. It could sign in and poke around. Asking for a privileged action got it turned down by the application before the request went anywhere. Signing in had established who it was, which is not the same as being allowed to ask for things.
Given permission to ask. The application now let the request through — and the host threw it out, because as far as the host was concerned, no such person existed. The application had authorised a request. It had no power to authorise execution, and commendably did not pretend otherwise.
Given a local account on that host. Now the host worked out who it was, collected its groups, and let it in the door. Third boundary open. Still nothing had actually run.
Given privilege to run the thing. Only now did the operation execute — and the record of that privileged step names the local identity, not the application principal, and certainly not the claim I had rewritten.
Four tries, four different walls, each exactly one step further than the last:
| Authority granted | Where it stopped |
|---|---|
| Authentication only | Application authorization |
| + permission to ask | Host identity |
| + a local account | Host privilege |
| + privilege to execute | Nothing. It ran. |
Which is when I found out it had a name
Written out afterwards, that is a zero-trust authorization chain:
APP AUTHENTICATION "Who are you?"
↓
APP AUTHORIZATION "May you request an action?"
↓
HOST IDENTITY "Are you a valid identity on this host?"
↓
HOST PRIVILEGE "May this identity execute this operation?"
↓
EXECUTION
I would love to say I designed that. I did not. There was never a four-gate model, or a diagram, or a decision that authority should be re-established four separate times. I had never used the phrase to design any of it.
TBH, I’ve never understood wtf zero trust meant.
What I can tell you is exactly what each of those four boxes refuses to assume, and why, and what happens when it refuses. That has turned out to be considerably more useful than the phrase.
Most of what gets built here starts with requirements I write end to end — the whole flow, start to finish — and hand to an agent to implement. That is the normal way around, and it means the shape of the result is a shape I chose in advance.
This was not that. Each of these boundaries got its own requirements, written at a different time, answering one narrow question: what does a caller have to prove here, and what does proving it actually get them? I was not thinking about what came before or what came next. I was thinking about what this particular component was entitled to assume, and the honest answer at every one of them turned out to be not much.
The identity provider works out who someone is. The entitlement decides whether they may ask. The host decides whether the asker corresponds to anyone it has heard of. Privilege on the host decides what that local identity may do. Four narrow answers to four narrow questions.
Answering them honestly, one at a time, is apparently the whole idea. The name describes the consequence of never letting one component's conclusion stand in for another's — which was not a strategy I adopted. It is just what you get when nobody writes the part that says and this one can skip the check, because the last one already did it.
What fell out of them is the bit worth keeping:
Authentication does not imply application authorization. Application authorization does not imply host identity. Host identity does not imply host privilege. No upstream assertion substitutes for the decision required at the next boundary.
I did not know that was true of this system until a request walked the entire chain and got stopped at every gate along the way.
Why discovering it beats asserting it
An architecture diagram claiming four independent gates is worth very little. It describes an intention. Whether the gates are actually independent — whether the third would still refuse if the second had been fooled — is a different question, and not one a diagram can answer about itself.
The progression answers it. Each boundary was observed refusing a request that had already satisfied every boundary above it. The code can tell you what each component is designed to do. The progression showed me what the composition actually did.
It is also the shape of the failure worth worrying about. Trust shortcuts do not announce themselves. They look like a component sensibly reusing a determination something upstream already made, and they stay invisible until the upstream determination is wrong — at which point every layer below inherits the error while continuing to report success.
What this does not prove
One principal. One rewritten claim. Four boundaries, on one path, for one kind of operation.
That is a real result and it is not a security audit. It says these four gates held, in this sequence, against this manipulation. It says nothing about paths I did not walk, claims I did not rewrite, or boundaries elsewhere in the system that were never exercised. A property demonstrated once is a property demonstrated once.
The honest summary is narrower than the diagram: an accident produced evidence that four independently built contracts compose into something stronger than any of them alone, and that a rewritten identity claim did not survive contact with the first component entitled to disbelieve it.
The part I keep coming back to is where the evidence came from. I was not testing security. I was trying to stop a simulator from eating my passkeys, and the system told me something true about itself because I had accidentally asked it the right question.
And then, as usual, I found out other people had been there for years and had a name for it. I have decided this is fine. Arriving at a known-good shape by reasoning about the problem is slower than reading the literature, and it leaves me unable to use the shorthand in a room where everyone else can. But it has one property the shortcut lacks: I can say exactly why each boundary is there, because I remember the question it was answering.
The name still does not do much for me. The reasons do.