No Cross-Tenant Data Leakage in List Endpoints
Why it matters
List endpoints without mandatory tenant scope filters are the highest-impact cross-tenant leakage vector: a single GET /api/projects call returns every project in the database if the tenant filter is missing or overridable. CWE-284 (Improper Access Control) and CWE-639 (Authorization Bypass Through User-Controlled Key) apply when the tenant filter can be bypassed by request parameters. OWASP A01 (Broken Access Control) lists this as a primary multi-tenant failure. NIST 800-53 AC-3 and SC-4 (Information in Shared Resources) require that tenant boundaries be enforced at the data layer. Search and autocomplete endpoints are a particularly common blind spot — they are often added quickly and the tenant filter is forgotten.
Severity rationale
High because a missing tenant filter on a single list endpoint exposes every record in the affected table to any authenticated user, across all organizations in the system.
Remediation
Hard-code the tenant filter from the session in every list query. No query parameter — including search terms — may override or remove it.
// app/api/projects/route.ts
export async function GET(req: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const { searchParams } = new URL(req.url);
const search = searchParams.get('q') ?? '';
const projects = await db.project.findMany({
where: {
orgId: session.user.activeOrgId, // Non-negotiable — never from user input
name: { contains: search, mode: 'insensitive' },
},
});
return NextResponse.json(projects);
}
Audit search and autocomplete endpoints specifically — they are the most common location for missing tenant filters because they are often added as a fast follow-up feature.
Detection
-
ID:
no-cross-tenant-leakage -
Severity:
high -
What to look for: Examine list/index API endpoints (
GET /api/projects,GET /api/users,GET /api/invoices) in multi-tenant routes. Check whether results are filtered by the active tenant. Also look for search or autocomplete endpoints — these are a common vector for cross-tenant leakage because filtering is sometimes omitted for performance or simplicity. -
Pass criteria: All list endpoints in multi-tenant contexts always include the tenant ID filter. Search and autocomplete endpoints are scoped to the current tenant. No query parameter can override or remove the tenant scope filter.
-
Fail criteria: Any list endpoint returns data from multiple tenants, or a query parameter (e.g.,
?orgId=all) can override the tenant scope filter. -
Skip (N/A) when: Multi-tenant concept not detected.
-
Detail on fail:
"List endpoint at [route] may return cross-tenant data. Tenant scope filter is absent or overridable."(Note the specific endpoint and how the filter can be bypassed.) -
Remediation: The tenant filter must be non-negotiable — it cannot be omitted or overridden by any user-supplied query parameter. Hard-code the tenant filter from the session in every list query.
// app/api/projects/route.ts export async function GET(req: NextRequest) { const session = await auth(); if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { searchParams } = new URL(req.url); const search = searchParams.get('q') ?? ''; const projects = await db.project.findMany({ where: { orgId: session.user.activeOrgId, // Non-negotiable — never expose this to user input name: { contains: search, mode: 'insensitive' }, }, }); return NextResponse.json(projects); }For a deeper analysis of multi-tenant isolation patterns, the Multi-Tenancy Audit in the SaaS Readiness Pack covers cross-tenant leakage in detail.
External references
- cwe · CWE-284 — Improper Access Control
- cwe · CWE-639 — Authorization Bypass Through User-Controlled Key
- owasp:2021 · A01
- nist:rev5 · AC-3
- nist:rev5 · SC-4
Taxons
History
- 2026-04-18·v1.0.0·Initial import from saas-authorization·automated