Data export endpoints that query broadly and filter in application memory load every tenant's records into a single server process — a cross-tenant data commingling event that violates CWE-285 and OWASP A01. For GDPR Art. 20 (Data Portability) compliance, the exported data must be scoped to the requesting tenant's own records. Beyond compliance, an export endpoint that generates a file for Tenant A and returns a download link that Tenant B can also use constitutes a direct data breach with a durable artifact.
Low because export endpoints are typically authenticated and require deliberate action, but successful exploitation produces a durable file artifact containing another tenant's data.
Always scope export queries at the database level and validate tenant ownership on download of async-generated export files:
// Scope the query — never filter in application memory
const data = await db.project.findMany({
where: { organizationId: session.user.organizationId },
include: { documents: true, members: true }
})
// For async exports, tie the job to the org and validate on download
const exportJob = await db.exportJob.create({
data: { organizationId: session.user.organizationId, status: 'pending' }
})
// Download handler — verify ownership before serving the file
const job = await db.exportJob.findFirst({
where: { id: params.jobId, organizationId: session.user.organizationId }
})
if (!job) return new Response('Not found', { status: 404 })
Place export logic in src/app/api/export/route.ts and audit it separately from general data routes — export endpoints are high-value targets because they return bulk data in a single response.
ID: saas-multi-tenancy.tenant-management.tenant-data-export-scoped
Severity: low
What to look for: Examine data export functionality — CSV downloads, JSON exports, GDPR data export, backup download endpoints. Check whether export endpoints scope the exported data to the requesting tenant's own data only. Look for export handlers that query broadly and then filter in application code (risky — all data is loaded), versus handlers that query with tenant scope in the database query.
Pass criteria: Enumerate all data export endpoints. At least 100% of data export endpoints use tenant-scoped database queries to retrieve only the requesting tenant's data. Export files are generated with only tenant-owned records. If export runs asynchronously (generating a downloadable file), the download link is scoped to the requesting tenant and cannot be shared with users from other tenants to access the same export.
Fail criteria: Export endpoints query all data and filter in application code, loading all tenants' data into memory. Export download links are not validated for tenant ownership when the file is downloaded — any authenticated user with the link can download another tenant's export.
Skip (N/A) when: No data export functionality is detected. Signal: no export endpoint, no CSV/JSON download handler, no "export my data" feature.
Detail on fail: Example: "Data export in src/app/api/export/route.ts queries all records without organizationId filter and filters them in application memory. For large deployments, this loads all tenants' data into one server process."
Remediation: Scope export queries at the database level:
// Always query with tenant scope, not in-memory filter
const data = await db.project.findMany({
where: { organizationId: session.user.organizationId },
include: { documents: true, members: true }
})
// For async exports, associate the export job with the org and validate on download
const exportJob = await db.exportJob.create({
data: {
organizationId: session.user.organizationId,
status: 'pending'
}
})
// On download, verify the requesting user's org matches the export job's org