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.
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.
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.
ID: saas-authorization.admin-privilege.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.