Multi-tenant applications that derive the active tenant ID from a client-supplied header, query parameter, or request body — rather than from the authenticated session — allow any authenticated user to access any organization's data by supplying that org's ID. CWE-284 (Improper Access Control) and CWE-639 (Authorization Bypass Through User-Controlled Key) both apply. OWASP A01 (Broken Access Control) lists tenant isolation failures as a primary risk. NIST 800-53 AC-3 and SC-4 (Information in Shared Resources) require that tenant boundaries be enforced by the system, not honored on trust from the client.
High because any authenticated user can access any tenant's data by supplying a valid org ID — no brute force needed if org IDs are discoverable or guessable.
Derive the active tenant from the authenticated session — never from a user-supplied request parameter. If users can belong to multiple orgs, store the active org selection server-side.
// Safe — tenant from session
const orgId = session.user.activeOrgId;
// Also safe — user supplies org ID but membership is verified first
const requestedOrgId = params.orgId;
const membership = await db.orgMember.findFirst({
where: { orgId: requestedOrgId, userId: session.user.id },
});
if (!membership) return NextResponse.json({ error: 'Not found' }, { status: 404 });
// Never do this without membership verification:
// const orgId = req.headers.get('x-org-id');
For org-switching flows, validate the switch on the server and update session.user.activeOrgId — do not let the client instruct the server which org to use by header.
ID: saas-authorization.admin-privilege.multi-tenant-isolation
Severity: high
What to look for: Count all relevant instances and enumerate each. In multi-tenant routes, trace how the orgId, teamId, or workspaceId is determined. Is it taken from the authenticated session (session.user.orgId, session.activeOrganizationId) or from a client-supplied value (req.headers.get('x-org-id'), params.orgId, body.orgId)? If the tenant context is derived from client input, is it validated against the user's membership before use?
Pass criteria: Tenant context (the org/team/workspace ID used to scope queries) is always derived from the authenticated session, not from client-supplied request parameters. At least 1 implementation must be verified. Or, if a user can specify which org to act on behalf of, the route verifies the user is a member of that org before proceeding.
Fail criteria: Tenant context is taken directly from a request parameter without validation — a user can supply any orgId and receive data from that organization's namespace.
Skip (N/A) when: Multi-tenant concept not detected. No teams, organizations, or workspaces in the data model or routes.
Detail on fail: "Tenant context is derived from client-supplied [header/param/body field] without membership verification. Users can supply any org ID and access that organization's data." (Note the specific parameter used.)
Remediation: Always derive the active tenant from the authenticated session. If users can belong to multiple orgs, store the active org selection in the session (server-side), not in a cookie or request header the user can modify.
// Safe — tenant derived from session
const orgId = session.user.activeOrgId; // Set server-side when user switches orgs
// Also safe — user supplies org ID but membership is verified
const requestedOrgId = params.orgId;
const membership = await db.orgMember.findFirst({
where: { orgId: requestedOrgId, userId: session.user.id },
});
if (!membership) return NextResponse.json({ error: 'Not found' }, { status: 404 });
// Unsafe — trusting client without verification
// const orgId = req.headers.get('x-org-id'); // NEVER DO THIS without membership check