saas-multi-tenancy
Skill4 starsChanged 4 months agoMITView on GitHub
Tenant isolation and lifecycle for multi-tenant SaaS — data scoping that fails closed, tenant context propagation, noisy neighbors, per-tenant operations. Use when building B2B/B2C SaaS with multiple customers on shared infrastructure, adding tenant-scoped tables or features, debugging cross-tenant data leaks, or when the user says "multi-tenant", "tenant isolation", "SaaS", "workspace", "organization data", or "row-level security".
In this repository23 other files it gives its agents.
- ai-agent-designSkill
- ai-build-qualitySkill
- ai-engineeringSkill
- api-contract-designSkill
- architecture-designSkill
- change-hygieneSkill
- code-qualitySkill
- database-designSkill
- data-privacy-complianceSkill
- dependency-disciplineSkill
- deployment-safetySkill
- docs-and-runbooksSkill
- legacy-code-changesSkill
- observability-readinessSkill
- payments-and-moneySkill
- performance-improvementSkill
- product-ux-qualitySkill
- release-readinessSkill
- requirements-to-specSkill
- root-cause-debuggingSkill
- system-designSkill
- testing-strategySkill
- threat-model-securitySkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 30
Background jobs: every enqueued job carries its tenant_id explicitly, and the worker re-establishes tenant context before touching data.
- Distinctive: line 31
Caches: every cache key of tenant-scoped data includes the tenant (system-design).
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
SaaS Multi-Tenancy
In a multi-tenant system, the worst bug class isn't downtime — it's tenant A seeing tenant B's data. One leaked invoice ends the customer relationship and possibly the company. Design so that isolation failures are structurally impossible, not just carefully avoided, because "carefully" doesn't survive the 400th endpoint.
Choosing the isolation model (decide once, explicitly)
- Shared schema +
tenant_idcolumn — the default. Cheapest to operate, easiest to migrate, scales to thousands of tenants. Requires the discipline below. - Schema-per-tenant / database-per-tenant — buy isolation with operational pain (N× migrations, connection management, cost). Justified by: hard compliance walls, wildly divergent tenant sizes, or contractual "your data is physically separate". Don't drift here for comfort.
- Hybrid (big tenants carved out) is legitimate — but only after the shared model measurably strains.
The rest of this skill assumes shared-schema, where the discipline matters most.
Gate 1: Scoping that fails CLOSED
The core principle: a forgotten WHERE clause must return nothing, not everything.
- Two layers, always: application-level scoping (every query goes through a tenant-scoped session/repository that injects
tenant_id— never hand-written per query) AND database-level enforcement (Postgres RLS withFORCE, policies keyed off a per-request setting, app connecting as a role that cannot bypass RLS — superusers silently bypass it; verify the role, and boot-guard against misconfiguration). tenant_id NOT NULLon every tenant table + FK to the tenants table + composite indexes leading with it (database-design). New tenant-scoped table = added to the RLS/policy list in the same migration, enforced by convention and a test that scans the schema for unpoliced tables.- The tenant comes from the authenticated principal, never from the request — no
?account_id=params, no tenant in the POST body, no trusting a JWT claim you didn't issue (threat-model-security Gate 1).
Gate 2: Context propagation — where leaks actually happen
Request handlers are the easy part. Cross-tenant leaks live in the places that don't have a request:
- Background jobs: every enqueued job carries its
tenant_idexplicitly, and the worker re-establishes tenant context before touching data. A worker processing a loop of tenants must reset context per iteration — context bleeding between loop iterations is the classic leak. - Caches: every cache key of tenant-scoped data includes the tenant (system-design). Query caches cleared on login/logout — stale cross-account data in an SPA is this bug wearing a frontend costume.
- Scheduled/cron jobs, exports, report generators, search indexes, LLM/RAG retrieval (ai-engineering): each is a query path; each gets the same scoping audit as an endpoint.
- Logs and error trackers: tag entries with tenant (great for debugging) but treat aggregated views as cross-tenant data needing admin-level access.
Gate 3: The two-tenant test — the cheapest breach prevention that exists
In your test suite, permanently: seed tenant A and tenant B, then as A's user attempt to read, update, delete, and list B's objects on every resource type — expecting 404/empty, never data. Add the list-endpoint case explicitly (unscoped lists leak in bulk). Run it in CI; every new resource joins it. Before any launch, run it live against production-like data (release-readiness Gate 2). This one fixture catches the bug class that code review misses.
Gate 4: Tenant lifecycle is a feature set, not an afterthought
Design these before the first real customer, because retrofitting them is surgery:
- Provisioning: idempotent tenant creation (seed data, defaults, numbering sequences) — one function, tested.
- Suspension (unpaid/abusive): a
blocked/suspendedflag checked at auth time, gating the whole app — data preserved, access stopped, reversible. - Export: a tenant can get their data out (their invoices, parties, ledgers) in a usable format. Contractually expected; also your data-privacy-compliance obligation.
- Purge: hard deletion of a departed tenant — everywhere: rows, files/objects, search indexes, caches, backups-policy documented. An orphaned tenant's data is pure liability. Purge is destructive: confirm, audit, and stage it (soft-delete window → hard purge).
Gate 5: Tenants are not the same size — plan for the whale
- Noisy neighbor: per-tenant rate limits on expensive endpoints, caps/pagination on unbounded queries (that one tenant with 500k products will find your missing LIMIT), and background-job queues that can't be monopolized by one tenant's bulk import (fair scheduling or per-tenant concurrency caps).
- Observe per-tenant: your metrics need a tenant dimension on the hot paths — "the API is slow" vs "tenant 4 is running 80% of load" are different incidents (observability-readiness).
- Per-tenant configuration (features, limits, branding) lives in a tenants/settings table read at runtime — never in code branches naming specific customers.
Cross-tenant admin operations
Your own admin/support panel is deliberately cross-tenant — which makes it the highest-privilege attack surface you own: separate principal type and stronger auth, every cross-tenant read/write written to an append-only audit log (who viewed which tenant's what, when), and impersonation (log-in-as-tenant), if built, is time-boxed, visibly flagged in the UI, and audited. Support convenience is how breaches get built with good intentions.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

