← all articles

One Code Path Can't Hold Two Levels Of Trust

One Code Path Can't Hold Two Levels Of Trust

If two different kinds of caller can reach the same piece of code, and that code trusts something one of them can be relied on to send honestly, you have to assume the other one will send it dishonestly. That sounds obvious written down. It's almost never obvious in the code, because the code doesn't show you the caller it wasn't written for.

I ran into a clean version of this recently, reviewing a change that let a service assert, via a request header, which customer's data it wanted. The design was sound for the caller it was built around: an internal gateway that terminated every external request, and stripped that header before anything reached the application if the request hadn't already been authenticated and re-issued with the correct value. Trust the header, because nothing untrusted could ever set it. That's a reasonable boundary, and for a while it held.

The trouble arrived with a second caller. A separate integration used a long-lived shared credential to call the same internal API directly, bypassing the gateway entirely because it was, itself, considered a trusted internal system. Someone wired that integration into the same code path that read the tenant header. Nobody stripped the header on that route, because stripping it had only ever been the gateway's job. The result was that anyone holding that one shared credential could set the header to any customer they liked and the application would believe them. Not a bug in the traditional sense — nothing crashed, nothing threw, the tests that existed all passed. Just a piece of trust logic operating exactly as designed, for a caller it was never designed for.

This is worth sitting with, because the failure mode isn't "insufficient validation." The header value wasn't malformed, wasn't out of range, wasn't anything a validator would flag. The failure was architectural: two callers with genuinely different trust levels were funnelled through one code path that only knew how to handle one of them. The stronger trust level — enforced upstream, invisible to the code that relied on it — quietly became the weaker one, because the code had no way to tell which caller it was talking to. Security boundaries that live outside the code they protect have a habit of doing that. They work until something reaches the protected code by a route the boundary doesn't cover, and then they've never really existed at all.

The fix wasn't to trust the header less. It was to make the code ask a different question first: which credential authenticated this request, and does that credential's trust model even permit it to make this particular claim? Once that question sat in front of the header-reading logic, the shared-credential path had an honest answer — no, that caller doesn't get to assert a tenant, full stop — rather than an implicit yes it had been getting by accident. The header stayed useful for the caller it was designed for. It just stopped being load-bearing for everyone else.

What I keep coming back to is how invisible this class of problem is in a normal review. You read the diff, the header-trusting code looks fine in isolation, the tests for the intended path are green, and the reviewer's attention naturally follows the feature being built rather than the other things that might call into it. The habit that actually catches this is a specific, slightly annoying question: if a different kind of caller — a different credential, a different service, a different trust level — hit this exact same code, what would happen? Not "is this code correct," but "who else can reach it, and did I just assume they're the caller I had in mind." That question doesn't come from a checklist. It comes from having been burned by the gap between what a system was designed to do and what it was actually capable of doing once it shipped.

There's a broader point underneath the technical one, which is why this kind of thing sits close to how I think about protecting data people have trusted an organisation with. Access control failures rarely look like someone breaking in. They look like someone using a door that was left unlocked because everyone assumed a different door was the one that mattered. The shared credential in this story wasn't malicious and wasn't even misused — the gap existed regardless of whether anyone ever walked through it. That's the uncomfortable part of security work: you're not just fixing the incident you can see, you're accountable for the incident nobody has tried yet. Waiting for evidence of harm before treating a gap like this as serious gets the responsibility backwards. The organisation asked customers to trust it with their data before anyone knew whether the door would hold.

None of this required exotic tooling or a security specialist parachuting in. It required a reviewer willing to trace a code path to every caller that could reach it rather than just the one the pull request was about, and a team willing to treat "this works for the caller we built it for" as an incomplete sentence rather than a finished one. Most systems accumulate more callers over time than they had on the day they were designed — new integrations, new internal services, new shortcuts that seemed harmless because they reused something that already existed. Every one of those additions is a chance for a trust boundary that used to be true to quietly stop being true, without a single line of the original code changing at all. The discipline isn't writing more defensive code everywhere. It's periodically asking, of the trust decisions already baked into a system, who else can reach this now — and whether the answer still matches the assumption the code was written under.

Matthew Ratcliffe, software developer and architect, Ballarat
Senior Software Engineer & Architect

20+ years across the technology stack — from greenfield builds to brownfield rescues. Based in Ballarat, VIC, focused on AI, healthcare and high-risk data systems. Full resume →

Share your thoughts