[ OFFENSE ]

The tenant-isolation bug that keeps showing up

A pattern shows up often enough in multi-tenant applications that it’s worth naming: an API endpoint that resolves which tenant a request belongs to using a value the client controls — a header, a query parameter, sometimes just a tenant ID in the request body — rather than deriving it from the authenticated session.

The result is predictable. Change the value, get another tenant’s data. No exploit chain, no clever bypass — just an assumption that never got tested.

Where to look first

Any endpoint whose name hints at cross-tenant resolution is worth a close read: resolve-tenant, switch-organization, account-context. These exist because someone needed a legitimate way to move between tenants — support staff impersonating a customer, an admin panel, an SSO callback deciding which org a token belongs to. The legitimate need is real; the authorization check protecting it is often not.

POST /api/v1/master-tenant-resolve
{ "tenant_id": "acme-corp" }

If that request succeeds regardless of which account is authenticated, the check is missing — not weak, missing.

Why it survives code review

The vulnerable code usually looks reasonable in isolation. The tenant ID gets validated (it exists, it’s well-formed), logged, even rate-limited. What’s absent is a comparison against the caller’s actual entitlements, and that absence doesn’t show up in a diff the way a missing input filter would.

The fix is one authorization check, but finding the gap means asking a different question than “is this input validated” — it’s “does the server trust the client to tell it who the client is.”