IDOR Vulnerabilities in Multi-Tenant SaaS: How to Find and Fix Them
IDOR vulnerabilities in multi-tenant SaaS expose every customer's data. Learn to find and fix broken object-level authorization in your product today.
IDOR — Insecure Direct Object Reference — is the OWASP API Security Top 10's number-one vulnerability category (API1:2023 Broken Object Level Authorization, or BOLA), and in 2026 it remains one of the most exploited weaknesses in production SaaS products. Shelf, ShellHub, Dokploy, ChromaDB, and OpenReplay all received critical or high-severity IDOR CVEs this year, each following the same pattern: a multi-tenant application trusted a resource identifier from the request without verifying the requesting tenant actually owned that resource. In a shared-infrastructure SaaS product, a single missed authorization check can expose one tenant's data to every other authenticated user on the platform.
This post covers what IDOR looks like in a SaaS context, the five patterns that appear most often in security audits of multi-tenant products, how to test for them systematically, and the architectural controls that prevent them at the data layer rather than hoping the route layer catches every case. If you are preparing for an enterprise security review or hardening a SaaS product built with AI coding tools, the SaaS pre-launch security checklist covers the broader surface; this post goes deep on broken access control specifically — the category that causes the largest breaches.
What Is an IDOR Vulnerability in a SaaS Product?
An IDOR vulnerability exists when your application exposes a resource by a predictable or user-supplied identifier — a UUID, integer ID, or slug — and the authorization check is absent, incomplete, or applied on the wrong layer. A typical pattern: GET /api/invoices/48271. If the API verifies only that the request has a valid authentication token but does not verify that the authenticated user belongs to the same tenant as invoice 48271, any authenticated user can read any invoice on the platform by guessing or enumerating the ID. In a multi-tenant product this is not a hypothetical edge case — it is a cross-tenant data breach affecting every customer simultaneously.
The OWASP API Security project renamed this category BOLA (Broken Object Level Authorization) to make the authorization framing explicit: the object exists, the user is authenticated, but the level of authorization — does this user own this specific object — is the check that is broken. The distinction matters because IDOR is sometimes confused with path traversal or resource enumeration; BOLA is specifically about authorized access to an object the requester does not own.
Why Multi-Tenant SaaS Is Especially Vulnerable to IDOR
Single-tenant applications — one database per customer, isolated infrastructure — have a structural defense against IDOR: a user can only reference objects in their own database partition. Multi-tenant SaaS applications by definition store all tenants' data in the same database, accessed by the same API layer, authenticated by the same identity provider. Every API endpoint that returns or mutates a resource by ID is a potential cross-tenant boundary. Authorization must be explicit and consistently enforced across every endpoint, every HTTP method, and every access path — including batch endpoints, nested resource endpoints, and background job handlers that process records by ID.
- →Shared database pressure: the same table holds records for all tenants. A query for record ID 48271 returns data regardless of which tenant's session made the request, unless the WHERE clause explicitly filters on tenant_id and the API layer verified that tenant_id matches the session.
- →Client-supplied tenant context: some SaaS APIs accept a tenant_id or organization_id in a header or query parameter to support multi-org users. If the API trusts that value without verifying it against the session token, any user can inject a different tenant's ID and retrieve or modify their data.
- →Inconsistent enforcement across HTTP methods: many codebases apply GET authorization correctly and miss PUT, PATCH, or DELETE on the same resource. The Shelf CVE (2026, CVSS 7.1) is a clear example — reading a record was gated, but modifying it was not, meaning a cross-tenant attacker could modify records they could not read directly.
- →AI-generated code and route-level guards: AI coding assistants scaffold authentication middleware reliably but consistently miss object-level authorization on mutation routes and nested resources. A Cursor or Lovable-generated API handler for GET /api/projects/{id} will typically check session validity but not project ownership.
- →Batch and bulk endpoints: endpoints that process arrays of IDs in a single request — bulk delete, batch update, export — are where per-object checks are most commonly skipped. A single batch request containing IDs from multiple tenants can leak or corrupt cross-tenant data at scale.
The Five IDOR Patterns That Appear Most in SaaS Audits
Across production SaaS security audits, broken access control concentrates in a predictable set of patterns. Finding them requires a systematic test methodology, not just scanning known routes.
- →Pattern 1 — Direct ID manipulation: the resource ID appears in the URL or request body and the API trusts it directly. Test: create two tenant accounts. Authenticate as Tenant A, capture a request to /api/records/{id}. Substitute an ID belonging to Tenant B. If the response returns Tenant B's data, the vulnerability is confirmed.
- →Pattern 2 — Tenant header injection: the application accepts a tenant_id or org_id header to determine context. Test: authenticate as Tenant A, set the header to Tenant B's ID. If the response returns Tenant B's data or accepts writes into Tenant B's namespace, the API is trusting client-supplied tenant context rather than deriving it from the verified session.
- →Pattern 3 — Nested resource traversal: access to a parent resource is checked but access to a child resource is not. Example: /api/projects/{project_id}/documents/{doc_id} — the project ownership check passes, but the document check is absent. An attacker supplies their own project_id and a document_id from another tenant's project. Every nested resource endpoint is a separate authorization surface.
- →Pattern 4 — Mutation without read guard: read access is correctly gated but write operations (PUT, PATCH, DELETE) on the same resource are not. Test: attempt to update or delete a resource owned by Tenant B while authenticated as Tenant A. If the operation succeeds with a 2xx response, the write path is unguarded.
- →Pattern 5 — Batch endpoints with missing per-object checks: a bulk operation processes an array of IDs. The endpoint checks that at least one ID belongs to the authenticated tenant but does not check every ID. Test: submit a batch request containing a mix of IDs — some from Tenant A, one from Tenant B. If the response processes all of them, the per-object check is missing.
How to Test for IDOR Systematically
Manual IDOR testing requires two fully provisioned tenant accounts with separate sessions. This is not optional: you need real cross-tenant IDs to substitute, not guessed values. A systematic test covers every API endpoint that accepts a resource identifier — including export endpoints, webhook subscription endpoints, and file download endpoints that teams routinely miss. For an automated first pass, DeployReady — Belsoft's open-source CLI — checks for common IDOR patterns, missing authorization headers, and exposed admin routes as part of its 30+ security checks. Run it as the baseline before a manual review. The manual cross-tenant test adds what static analysis cannot catch: real session-level access across the tenant boundary.
- →Step 1 — Map all resource endpoints: list every endpoint that returns or accepts a resource identifier. Include REST paths, GraphQL queries and mutations, and export or report endpoints that accept filter parameters. Batch endpoints and webhook handlers are frequently missed.
- →Step 2 — Provision two tenant accounts: create complete accounts for Tenant A and Tenant B with distinct data — records, files, settings — so cross-tenant retrieval is verifiable by inspecting the response content, not just the status code.
- →Step 3 — Run the cross-tenant matrix: for every endpoint, authenticate as Tenant A and substitute a resource ID belonging to Tenant B. Test GET, PUT, PATCH, DELETE, and POST actions separately. A 200 with Tenant B's data is a confirmed IDOR finding.
- →Step 4 — Test mutation under read guard: for each resource where GET returned a correct 403 cross-tenant, attempt PUT and DELETE. A system that correctly returns 403 on GET but 200 on DELETE has a partial guard — write operations are still unprotected.
- →Step 5 — Verify batch and export endpoints: submit batch requests with mixed tenant IDs. Test export and report endpoints with filter parameters set to Tenant B's values while authenticated as Tenant A.
- →Step 6 — Test header injection: on every endpoint that reads a tenant_id, org_id, or x-organization-id header, replace the value with Tenant B's identifier while maintaining Tenant A's session cookie and authentication token.
How to Fix IDOR: Authorization at the Data Layer
Route-level guards catch unauthenticated requests. They do not prevent authenticated Tenant A from accessing Tenant B's data — that requires object-level authorization. The most reliable way to implement it is at the data layer, not the route layer. Route-level checks require every developer writing a new endpoint to remember to apply the check correctly. Data-layer authorization enforces the rule structurally — it becomes impossible to execute a query that returns another tenant's records, because the query itself is scoped by the tenant context from the session.
- →Derive tenant context from the session token, never from the request: the tenant_id must come from the verified JWT or server-side session, not from a header, URL parameter, or request body field. If a client-supplied tenant ID is needed for multi-org support, verify that the authenticated user has active membership in that organization before honoring the value.
- →Apply Row Level Security for database-enforced tenant isolation: Supabase, PostgreSQL, and most managed database providers support RLS policies that filter every query by tenant context at the database engine level. A SELECT on any table is automatically scoped to the current tenant without requiring application-layer code on each query.
- →Our Supabase RLS security guide covers the exact policy syntax for multi-tenant isolation — including the cases where RLS policies are present but misconfigured to allow bypass through elevated roles or SECURITY DEFINER functions.
- →Centralize authorization logic in a single module: a centralized authorization function — can(user, action, resource) — called before any data operation ensures the check is applied consistently and is easy to audit. Scattered authorization checks across route handlers, service functions, and database queries create inconsistency that becomes exploitable over time.
- →Validate resource ownership before mutation: before processing a PUT, PATCH, or DELETE, load the resource and verify its tenant_id matches the session's tenant. Do this in the service layer, not the route handler, so it applies regardless of how the resource is accessed — API, background job, webhook handler, or admin script.
- →Apply the same check to every ID in batch operations: for any endpoint that processes an array of IDs, verify ownership of every ID in the array. Return 403 if any ID belongs to a different tenant; do not silently skip or filter it out, as silent filtering conceals the attack from your own monitoring.
IDOR in AI-Generated SaaS Code
A disproportionate share of IDOR findings in SaaS audits now involve code generated by AI coding assistants. Lovable, Bolt, Cursor, and similar tools generate authentication middleware reliably — they scaffold JWT verification, session handling, and protected-route patterns correctly in most cases. What they consistently miss is object-level authorization on individual resource endpoints, particularly on mutation routes and nested resources. A Cursor-generated API handler for GET /api/projects/{id} will typically check session validity but not project ownership. The POST /api/projects/{id}/members handler for the same resource will often have neither check. The authentication and authorization mistakes vibe-coded apps make most often covers the broader pattern across tools; IDOR is the most common and most severe manifestation.
When auditing AI-generated SaaS code for IDOR: check every handler that reads a path parameter — does it verify the resource belongs to the session's tenant before returning or mutating it? Check every mutation endpoint for an explicit ownership check, separate from the authentication check. Check every bulk or batch endpoint for per-item verification. AI assistants compress implementation time significantly, but they do not apply authorization depth consistently across the full API surface. That gap is where most findings are.
Frequently Asked Questions
What is an IDOR vulnerability in SaaS?
An IDOR (Insecure Direct Object Reference) vulnerability in SaaS occurs when an API endpoint exposes a resource by a user-supplied identifier — a UUID, integer ID, or slug — without verifying that the authenticated user's tenant actually owns that resource. In a multi-tenant product, this means any authenticated user can access any other tenant's data by substituting a resource ID in the request. OWASP classifies this as API1:2023 Broken Object Level Authorization (BOLA) — the top vulnerability category in the OWASP API Security Top 10.
What is the difference between IDOR and BOLA?
IDOR (Insecure Direct Object Reference) is the original OWASP web application security term for accessing a resource via a predictable identifier without adequate authorization. BOLA (Broken Object Level Authorization) is the OWASP API Security Top 10's more precisely scoped version of the same class of vulnerability. BOLA emphasizes that the failure is at the object level — the user is authenticated but not authorized for the specific object being requested — rather than at the endpoint or path level. In practice the terms describe the same vulnerability; the distinction matters for taxonomy and for how you frame the fix.
How do you test for IDOR in a multi-tenant application?
Create two fully provisioned tenant accounts. Authenticate as Tenant A and capture a request to any API endpoint that accepts a resource identifier. Substitute a resource ID belonging to Tenant B. If the response returns Tenant B's data or accepts a write to Tenant B's resources, the endpoint is vulnerable. Test every HTTP method — GET, PUT, PATCH, DELETE — separately, as mutation endpoints are frequently unguarded when only read access has been tested. Also test batch endpoints with mixed-tenant ID arrays, and any header that specifies a tenant context.
How do you prevent cross-tenant data access in SaaS?
The most reliable prevention is enforcing tenant isolation at the data layer with Row Level Security policies — database-engine-level filters that scope every query to the current tenant context automatically. Supplement RLS with application-layer ownership verification before every mutation: load the resource, check its tenant_id against the session's tenant_id, and reject the request if they do not match. Never trust a tenant ID supplied in a request header or body — derive it exclusively from the verified session token or JWT claims.
How common are IDOR vulnerabilities in production SaaS products?
Extremely common. OWASP ranks BOLA as the number-one API vulnerability category, and 2026 produced a steady stream of public CVEs in multi-tenant SaaS products: Shelf (CVSS 7.1, cross-organization read and write), ShellHub (cross-tenant IDOR on two separate API endpoints), Dokploy (CVSS 9.6, exposed another tenant's S3 credentials), ChromaDB (CVSS 8.8, tenant filter bypassed by UUID), and OpenReplay (route-parameter casing mismatch bypassed the authorization check entirely). The vulnerability persists because object-level authorization is not enforced structurally in most frameworks — developers must apply it consistently by hand on every endpoint.
How Belsoft Helps SaaS Teams Fix Broken Access Control
Broken access control — IDOR, BOLA, and tenant isolation gaps — is one of the first things we look for in a SaaS security audit. The Belsoft security audit runs a manual cross-tenant authorization matrix against your API: two provisioned tenant accounts, every endpoint tested across all HTTP methods, every batch and nested resource endpoint evaluated for per-object checks. We also review your database's RLS configuration, your authorization module's architecture, and your AI-generated code paths for the specific patterns these tools reliably miss. The engagement includes fix support and a retest — the finding is not closed until the fix is verified.
For an immediate automated baseline, run DeployReady — Belsoft's open-source CLI that flags missing authorization headers, exposed endpoints, and common access control misconfigurations as part of its 30+ security checks. It runs entirely locally, scores your application from 0 to 100, and generates fix suggestions with before-and-after diffs. The manual audit adds what static analysis cannot: real cross-tenant access testing with live sessions. If your SaaS is approaching enterprise deals or handling a security questionnaire, book a scoping call and we will walk through your authorization architecture and identify the highest-risk gaps first.
“Authentication tells you who is making the request. Authorization tells you whether they are allowed to access this specific resource. Most SaaS breaches are not authentication failures — they are authorization failures one layer deeper.”
Written by
Marco Silva
Security Engineer, Belsoft Solutions
More from the blog
Ready to partner?
Let's talk about your company.
30 minutes. No pitch. We talk through how you run today and where AI and automation would help.
Book a free audit
