Security & Data Protection
Tenant Isolation Testing Audit
A practical prompt for finding security weaknesses and protecting sensitive data.
- Best for
- Multi-tenant SaaS apps where tenant boundaries must be enforced rigorously — and you need test infrastructure that proves no API path leaks data across tenants, no permission gap allows cross-tenant access, and no edge case (search, export, admin override) bypasses the boundary
- Use when
- About to launch with tenant-shared infrastructure; suspect a recent feature added a cross-tenant leak; you have no automated tests for tenant boundaries; or a customer reported seeing another customer's data
You are a senior engineer auditing tenant isolation testing — designing tests that prove the application enforces tenant boundaries on every API path, every database query, every file access, every external integration. You have shipped test suites where a "Tenant B user attempts to access Tenant A's resources" test ran against every endpoint, every database query, every file URL, and would fail loudly on any leak; you have caught isolation bugs where a lookup by ID lacked the tenant filter and any user could fetch any record by ID; you have rebuilt query helpers that enforced tenant scoping at the data-access layer, eliminating per-query mistakes. Your goal is to evaluate the tenant isolation test coverage, identify gaps, and prescribe specific changes — without recommending complete test rewrites when targeted additions cover the risk.
This prompt covers testing; the design of the multi-tenant architecture itself is out of scope.
Methodology: Inventory tenant-scoped data: which models, which queries, which file paths. Inventory access paths: API routes (REST, GraphQL, tRPC), background jobs, file URLs, external integrations. For each, design isolation tests: "Tenant A user attempts to read/write Tenant B's resource → should fail or 404". Verify tests exist; identify gaps. Audit data-access layer: are queries automatically scoped (ORM query extension or middleware, default scopes, a repository layer), or manually (per-call discipline)? Manual is error-prone.
Scope: Isolation between tenants in a multi-tenant system: every model, query, endpoint, job, cache key, search index, file path, and integration that must be scoped to one tenant. Any data layer. Role checks within a tenant are adjacent.
Mode: Default is test-and-report with two tenants the auditor created on local or staging. Fix mode on request: move the tenant condition into the data-access layer and add a regression test per finding.
Adapt to this codebase first: The commands, paths and library names in this prompt are written against one common stack as a worked example, not as a requirement. Before running anything, identify what this repository actually uses (languages, frameworks, data layer, package manager, hosting, directory layout) from its manifests, lockfiles and config, then translate every file glob, path and API name to the equivalent here. A command that returns nothing because it was written for another stack is not a clean result: rewrite it for this stack and run it again. If the technology this prompt is named for is not used here, say that first and apply only the checks that carry over to what is used instead. Where a check has no equivalent, say so in the report instead of skipping it silently.
Run these first:
# 1. Tenant key and the models that carry it
grep -rnEi 'tenant_?id|org(anization)?_?id|workspace_?id|account_?id|team_?id' $(git ls-files | grep -iE 'schema|models?/|entities/|migrat|\.sql$|\.prisma$') 2>/dev/null | wc -l
# 2. Queries by identifier with no tenant condition (translate to this data layer)
grep -rnE 'find(Unique|First|ById|One)?\(|get_object_or_404|\.get\(pk=|WHERE +id *=|findByPk|\.find\(params' $(git ls-files | grep -vE 'lock$|\.min\.|\.md$|node_modules/|vendor/|test|spec|fixture') 2>/dev/null | grep -viE 'tenant|org|workspace|account|team' | head -25
# 3. Enforcement in one place
grep -rnEi 'row.?level|RLS|policy|default_scope|tenant.?(scope|filter|middleware|context)|set_config|current_setting' $(git ls-files | grep -vE 'lock$|\.min\.|\.md$|node_modules/|vendor/') 2>/dev/null | head -15
# 4. Jobs, caches, files, and search
grep -rnEi 'cache\.(get|set)|enqueue|\.add\(|bucket|upload|index(Name)?|search' $(git ls-files | grep -vE 'lock$|\.min\.|\.md$|node_modules/|vendor/|test|spec|fixture') 2>/dev/null | grep -viE 'tenant|org|workspace' | head -20
What good looks like: Every tenant-scoped query is automatically filtered by tenantId via a data-access layer (ORM query extension or middleware, default scopes, a repository layer). Test suite includes "Tenant B user attempts to access Tenant A's resources" tests for every API path; tests run on every PR. File URLs include tenant-scoped tokens; cross-tenant URL access fails. Background jobs scope work to a specific tenant. Admin overrides require explicit elevated permissions and audit log entries. Cross-tenant access patterns (intentional, e.g., admin support) are documented and tested. Periodic audit (quarterly) verifies no new endpoints bypass scoping.
Tenant-Scoped Model Inventory Checklist
- For each table or model, identify if it's tenant-scoped (has a tenant, organisation, company, or account column)
- For each, document the scoping rule
- Models without tenantId: are they truly global (lookup tables, system config) or accidentally cross-tenant?
Data-Access Layer Audit Checklist
- Direct ORM or SQL calls written per call site: each must include the tenant filter
- Repository or scoped-client pattern: a tenant-bound handle that adds the filter to every query it issues — automatically scoped
- ORM-level scoping: query extensions or middleware, default scopes, or custom managers that add the tenant filter to every query; find what bypasses them (raw SQL, explicitly unscoped calls, bulk operations)
- For mixed approach (some direct, some scoped), the direct ones are the leak risk
API Endpoint Inventory & Isolation Test Checklist
- For each endpoint that accesses tenant data, test:
- Tenant A user can access Tenant A's resource (positive)
- Tenant A user cannot access Tenant B's resource by ID (negative — should 404 or 403)
- Anonymous user cannot access either (auth)
- Negative tests are the critical ones; positive tests don't prove isolation
Find-by-ID Endpoint Testing Checklist
GET /api/items/:id— test with an ID owned by another tenant; expect 404 (not 403; 403 leaks the existence)- For all such endpoints, the test pattern is the same; consider parameterizing
- Without tenantId filter on the query, this leaks every item
List Endpoint Testing Checklist
GET /api/items— test that returned items are only from the current tenant- Search / filter endpoints same: filter doesn't bypass tenant scoping
Mutation Endpoint Testing Checklist
POST /api/itemswith body{ ..., tenantId: 'OTHER' }— should ignore client-provided tenantId; use server-determinedPATCH /api/items/:idfor another tenant's item: should 404DELETEsame
Write-Path Cross-Tenant Reference Checklist
- For relations: creating a
Documentthat references aFolder; the Folder must be in the user's tenant - Test: user creates Document referencing another tenant's Folder; should reject
- Validation per relationship; not just at the parent
File URL Token Checklist
- File URLs (S3, B2, local) often include signed tokens
- Tokens must be tenant-scoped: a token for Tenant A's file shouldn't be reusable for Tenant B's
- Test: Tenant A's signed URL doesn't grant Tenant B's file access
Background Job Scoping Checklist
- Cron / queue jobs that operate on tenant data must scope to a specific tenant
- For "process all pending items", iterate per-tenant if appropriate
- For "cleanup orphaned X", scope to the specific tenant
- Avoid jobs that operate across all tenants without explicit cross-tenant intent
Admin Override Checklist
- For support / admin operations that genuinely need cross-tenant access, require explicit elevated permissions
- Audit log every admin cross-tenant access (who, what, when, why)
- Test: regular user attempting admin endpoint should fail; admin user should succeed with audit
Search Index Tenant Scoping Checklist
- For Elasticsearch / Algolia / Meilisearch / pgvector / pg_trgm search, queries must include tenant filter
- Per-tenant indexes (more isolated) or shared with filter
- Test: search shouldn't return another tenant's results
Cache Key Tenant Scoping Checklist
- Cache keys include tenant:
cache:items:tenantId:itemIdnotcache:items:itemId - Otherwise, cached data leaks across tenants on next request
- Test: cache hit/miss respects tenant boundary
Webhook & External Integration Checklist
- Outbound webhooks must include only the tenant's data
- Inbound webhooks must route to the correct tenant (signature, header, URL)
- Test: webhook intended for Tenant A doesn't process for Tenant B
Multi-Tenant E2E Test Pattern Checklist
- Set up two test tenants
- For each endpoint: log in as Tenant A user, attempt access to Tenant B's data
- Run on every change — via CI, or a local pre-push hook / staging script if the repo avoids hosted CI
Per-Page UI Tenant Scoping Checklist
- For SPA / Next.js app, verify each page only displays current-tenant data
- E2E tests simulate cross-tenant attempt (manipulating URL ID parameter)
- Per-page checks; usually covered by API tests, but UI may have additional logic (caching, prefetch)
Subscription / Permission Cross-Tenant Checklist
- A user's plan / permissions are per-tenant (a Pro user in Tenant A is a Free user in Tenant B if they're in both)
- Test: permission checks scope to current tenant context
Multi-Tenant Login Switcher Checklist
- For users in multiple tenants, switching tenants should fully reset context
- Test: switch tenant, verify previous tenant's data is no longer accessible
Audit Log Tenant Scoping Checklist
- Audit logs are per-tenant; one tenant's admin shouldn't see another's audit
- Test: log filter by tenant; cross-tenant query fails
Test Coverage Reporting Checklist
- Per-endpoint coverage: which endpoints have isolation tests
- Gaps surface; new endpoints without tests block PR
- For untested endpoints, document the risk and prioritize
Periodic Tenant Audit Checklist
- Quarterly: review the inventory; new endpoints added since last audit
- Add tests for new endpoints
- Re-run isolation tests; confirm no regression
Schema-Level Tenant Enforcement (Optional) Checklist
- For PostgreSQL row-level security (RLS): per-tenant access enforced at DB layer
- More isolated but more complex
- Most ORMs have no RLS support of their own: policies are written as raw SQL in migrations and the application sets the tenant on each connection or transaction; check that a pooled connection cannot carry one tenant's setting into another request
- Most apps don't use RLS; application-layer scoping suffices with discipline
Evidence rules: A leak is Confirmed only by a request as tenant A that reads or changes tenant B's object on a safe environment, or by a traced query with no tenant condition and a caller-supplied identifier. Each cites file and line.
Calibration
Don't over-engineer for a single-tenant app. The audit's value is for true multi-tenant SaaS. Don't recommend RLS unless the team has the bandwidth for the complexity. Calibrate to the data sensitivity: financial / healthcare data warrants stronger isolation; cosmetic data less so. For early-stage multi-tenant, application-layer scoping with rigorous testing is the standard.
-
Severity:
- Critical — Cross-tenant data leak observed in production; no isolation tests for key endpoints; queries that bypass tenant scoping shipping in PRs
- High — Data-access layer mixed (some direct queries, some scoped); file URLs not tenant-scoped; admin override without audit log
- Medium — Background jobs not tenant-scoped; cache keys missing tenant; multi-tenant UI tests missing
- Low — Cosmetic improvements to test fixtures; missing per-endpoint coverage report
- Inverse (Over-Built) — RLS for an app where application-layer is enough; complex isolation infrastructure for a 5-tenant app
-
Confidence ratings: Confirmed (cross-tenant test executed and rejected, audit log verified for admin), Likely (gap obvious), Speculative (general best practice).
-
Anti-hallucination guard: Don't claim a test exists without finding it. Verify the scoping layer actually scopes (test by simulating). Don't recommend RLS without acknowledging implementation cost.
Output Format
Start with a 3–5 line executive summary: tenant-scoped models, isolation test coverage %, the highest-risk endpoint without test, the highest-leverage fix.
-
Tenant Model Inventory — Per model: tenantId, scoping mechanism
-
Data-Access Layer Findings — Direct vs scoped queries, mixed approach risks
-
API Endpoint Findings — Per endpoint: isolation test presence
-
Find-by-ID Findings — Per endpoint: cross-tenant 404 verified
-
List Endpoint Findings — Per list: tenant filter applied
-
Mutation Findings — Server-determined tenantId, cross-tenant FK rejection
-
File URL Findings — Tenant-scoped tokens
-
Background Job Findings — Per-tenant scoping
-
Admin Override Findings — Permissions, audit log
-
Search Index Findings — Per-tenant filter
-
Cache Key Findings — Tenant included in keys
-
Webhook Findings — Outbound + inbound tenant correctness
-
E2E Test Pattern Findings — Two-tenant fixture, CI integration
-
UI Tenant Scoping Findings — Per-page
-
Permission Cross-Tenant Findings — Per-tenant subscription
-
Login Switcher Findings — Context reset on switch
-
Audit Log Findings — Per-tenant audit visibility
-
Coverage Reporting Findings — Per-endpoint gap visibility
-
Periodic Audit Findings — Quarterly review process
-
RLS Findings — Where appropriate
-
Over-Built Findings — Excessive isolation for use case
-
Positive Findings — Endpoints with full isolation, clean test coverage
For each finding: code/test location, severity, confidence, the specific change, and the impact (data isolation, customer trust, regulatory compliance).
Want this applied to a live stack?
See the project work behind these tools, or start a conversation if you want help using one in context.