Trust nothing at the door: why every function checks its own caller

Perimeter security assumes something upstream already checked the caller. That assumption is how a lot of breaches happen. How we think about authenticating inside every function — and the test that proves it.

A close view of a heavy wooden door latch, warm light from the room beyond catching its edge.

There is a comfortable assumption baked into a lot of backends: that by the time a request reaches your function, something upstream has already checked who is calling. A gateway, a middleware, a transport layer — surely one of them verified the caller. It is a comfortable assumption, and it is how a great many breaches happen. We build on the opposite premise: assume the caller is not who they claim, assume nothing in front of the function did its job, and make every function establish the caller itself.

Trust is not a property you can inherit

The problem with perimeter security is that the perimeter is porous and getting more so. Functions get called from new transports, from internal jobs, from a refactor that quietly routes around the gateway everyone assumed was mandatory. Each of those is a path where the 'someone upstream checked' assumption silently fails, and the function has no idea, because it was written to trust its caller. The only place a check cannot be bypassed is inside the function that does the sensitive work. So that is where we put it: every backend function authenticates its own caller in its own body, as if it were the only line of defense — because at the moment it runs, it is.

Make the rule checkable, not aspirational

A security principle that lives in a style guide is a suggestion. A security principle a machine enforces is a rule. The difference between the two is the difference between 'we try to remember to authenticate' and 'an unauthenticated call cannot succeed'. So the premise is not just written down; it is tested. A dedicated suite calls the real functions with no credentials at all and requires every one of them to refuse. It runs on every change, and again as a gate before anything can deploy. If a new function forgets to check its caller, it does not reach production — the test that assumes the worst catches it first.

The same instinct, everywhere

This is one instance of a pattern that runs through the whole system: when you are unsure, fail closed. An unverified caller is denied, not waved through. An unproven deploy is refused, not assumed safe. A cleanup job with nothing to confirm against does nothing at all. Security stops being a layer you bolt on and becomes the default the system falls back to when it does not know — which is exactly when you most need it to hold.

What this costs, and what it buys

Authenticating inside every handler is slightly more code than trusting a gateway, and writing the suite that proves it is real work. The trade is worth making. It buys a backend where the safety of a function does not depend on the correctness of everything in front of it, where a new transport or an internal refactor cannot quietly open a hole, and where 'is this endpoint protected?' is a question with a mechanical answer instead of a hopeful one. Trust nothing at the door, and you never have to wonder whether the door was locked.

This note describes the implementation as it stood when it was written. Figures are counted from the repository; they are not published benchmarks or a performance guarantee.