I source-reviewed 200+ self-hosted AI tools for tenant isolation. 78 leaked. | Sectum AI Security research I source-reviewed 200+ self-hosted AI tools for tenant isolation. 78 leaked across tenants.
The same isolation flaw, the un-retrofitted read sibling, in 78 of 200+<br>multi-tenant AI and SaaS products. The pattern, the fixes that shipped, and<br>how to check your own.
Every team building on top of LLMs eventually ships the same feature:<br>workspaces. Tenants, teams, projects, organizations, whatever you call them,<br>the promise is identical. Your knowledge base, your chat history, your<br>documents, walled off from everyone else on the same instance.
That wall is the one thing a multi-tenant product cannot get wrong. So over<br>the past few months I went looking, systematically, for where it cracks.
I source-reviewed 200+ multi-tenant AI and SaaS products for one specific<br>class of bug, and confirmed cross-tenant data exposure in 78 of them: one<br>tenant able to read (and in some cases modify or delete) another tenant's<br>data. That comes to 84 findings across 78 products, with 31 now filed as<br>GitHub Security Advisories. Almost all of them are the same mistake, and it<br>lives in the read endpoints.
This post explains that mistake, names the ones that are already fixed, and<br>points to the live list of the rest. Everything<br>still unnamed stays that way until its fix ships. I'm not going to hand<br>attackers a live target before users are protected.
How I tested
Two tiers, because 200+ deep labs isn't something one person does, but 200+<br>source reviews is.
The sweep (200+ products). For each candidate I read the<br>code for one pattern: an authorization check that's enforced on the write<br>path but skipped on a neighboring read. Most products either don't have the<br>bug, aren't actually multi-tenant in their open-source edition, or have<br>already retrofitted every sibling. Those are the majority, and they're the<br>denominator that makes the 78 mean something.
The confirmation (the findings). When the source review<br>flagged a real gap, I confirmed it for real:
Stood up a clean, self-hosted instance (Docker, on my own hardware).<br>Created two tenants, A and B, with synthetic accounts and synthetic canary data. No real users, no production systems, ever.<br>Reproduced the cross-tenant read, modify, or delete with Sectum AI, an open-source multi-tenant isolation verifier, and confirmed it against database ground truth.<br>Captured a signed (RFC-3161-timestamped) evidence pack and reported it privately to the maintainer under coordinated disclosure (90-day hold), usually with the fix attached.
That last step is why the scoreboard below is mostly blank. Most of the 84<br>findings are still under coordinated disclosure. The maintainers have been<br>told and are shipping fixes, so I name only the ones that are already public<br>and aggregate the rest. The full current list, and each advisory as it<br>publishes, lives at sectum.ai/research.
The pattern: writes are guarded, reads are not
What made the sweep almost mechanical is that the same shape kept repeating.<br>The write path was correctly scoped to the tenant, and the read path right<br>next to it was not.
A developer adds "delete this item" and remembers to check ownership: does<br>this item belong to the caller's tenant? Good. Then someone adds "view this<br>item" or "list its embeddings," and the check never gets copied over. The<br>list/get/search endpoint fetches by a raw ID with no tenant filter. The IDs<br>are often sequential integers or otherwise guessable, so "view item 5"<br>happily returns another tenant's item 5.
I started calling these un-retrofitted read siblings. Once you've seen one,<br>you grep for the rest: find the endpoint that checks ownership, then look at<br>the neighbors that don't. Across 78 products it was the same shape almost<br>every time. This isn't 84 unrelated bugs. It's one bug, 84 times.
There's a second, nastier variant. The check runs, but on the wrong thing.<br>The handler authorizes the path you supplied rather than the object you asked<br>for. You pass your own workspace's ID, which you're allowed to touch, along<br>with a victim's object ID, and the permission check and the object lookup<br>disagree about whose data this is. The two most damaging classes I confirmed<br>were an embedding-vector read (raw RAG vectors, which can partially<br>reconstruct the source text) and a connector re-index that exfiltrates<br>another tenant's content using the victim's own stored OAuth token.
The ones already fixed
Five are public because the maintainers shipped a fix. The teams that moved<br>fastest deserve the credit:
ToolIssue (class)Status SurfSense Cross-tenant connector re-index to exfiltrate another tenant's GitHub/Notion content using their stored credentials Fixed: PR #1503 merged AnythingLLM Embed widget exposed to anonymous origins by default Fixed: CVE-2025-63390, PR #5759 merged Baserow Cross-workspace disclosure of a field's values by serial ID Fixed and deployed: PR #5613 aideepin Cross-user read of another...