Skip to content
All writing

[ Findings ]

Tenant isolation: the bug class SaaS teams keep shipping

One account reaching another account's data is the finding we report most often, and it almost always arrives through the same three doors.
Published
29 July 2026
Written by
Vaptiq Testing Team
Length
3 min read

Written by

Vaptiq Testing Team

Lead penetration tester

CREST CRTOSCPOSWE

The short version

  • Automation finds surface bugs: 78% of valid findings from automated hackbots on HackerOne were cross-site scripting.
  • Access control and IDOR findings are rising as XSS declines, and they earn the highest payouts, because they need a human who understands the data model.
  • The fix is a scoped query at the data layer, not another guard clause.

One account reaching another account’s data is the most ordinary bug in multi-tenant software, and it keeps shipping because the check that prevents it is written in the wrong layer.

It also happens to be the bug class that survives every wave of automation, and there is now decent public evidence for why.

What machines find, and what they miss

HackerOne’s 2025 researcher signals report covers the first year in which automated “hackbots” submitted at meaningful volume. More than 1,100 hackbot submissions came in and nearly half were valid, a genuinely good hit rate. The interesting number is what they found.

78%
Of valid automated hackbot findings were cross-site scripting, a single pattern-matchable bug class
73%
Of programmes saw a drop in valid submissions as surface-level bugs ran out
Rising
Access control, IDOR variations and misconfiguration findings, as XSS declines from its 2024 peak
Highest
Payouts go to access-control and logic flaws, not surface findings

Four out of five machine-found bugs were one bug class, and it was the class you can recognise from the shape of a response. Tenant isolation is not in that list, and it never will be, because finding it requires knowing which records should have been invisible.

A scanner has one account. Tenant isolation needs two, plus the knowledge of which identifier means what.

How it usually looks

The main application is careful. Every controller for the primary resource checks that the requested record belongs to the caller’s tenant, and it does so correctly. Then a second entry point is added six months later: an export, a scheduled report, a webhook replay, an admin-facing search. The check does not come with it.

POST /v2/invoices/export HTTP/1.1
Authorization: Bearer <tenant-a-token>
Content-Type: application/json

{ "tenant_id": "tenant-b", "format": "csv" }
The shape of the request that proves it

The token is honest. The session is valid. The application simply trusted a tenant identifier that arrived from the client, because on this one endpoint nobody scoped the query. There is no malformed input for a scanner to notice. The request is well-formed and the response is a valid CSV.

The three doors

  • Export and reporting endpoints, often written by a different team against the database directly.
  • Webhook and integration replay, where the payload carries an identifier the application dereferences without re-checking ownership.
  • Internal admin tooling exposed on the same host, reachable with an ordinary user token because the only control is an unlinked URL.

The fix that actually holds

Stop enforcing tenancy in controllers. Enforce it in the data access layer, so every query is scoped by the caller’s tenant before a developer gets a chance to forget. Row-level security in the database, a repository that will not construct an unscoped query, a base query object that requires a tenant. Any of these work. Another guard clause does not, because the next endpoint will be written by someone who did not read this one.

  • Make the unscoped query impossible to express, rather than merely discouraged in review.
  • Never accept a tenant identifier from the client. Derive it from the session on every request.
  • Use opaque, non-sequential object identifiers so a missing check is not trivially walkable.
  • Add a regression test with two tenants that asserts a 404, and run it against every new endpoint.
A control enforced in ten places is enforced in nine places by this time next year.

When we report this, the report includes the exact request, the response containing the other tenant’s data, and the specific place in your stack where the scope should live. Not a severity rating and a link to OWASP.

The engagement that covers this

Find out what is reachable, before somebody else does.

Thirty minutes is usually enough to scope an engagement and put a fixed price in writing. You talk to someone who tests, not a sales engineer.