Not every SaaS customer needs a dedicated account—but enterprise tenants do need stronger than WHERE tenant_id = ?. The unfair advantage is an explicit tier ladder (pool → bridge → silo) with IAM, KMS, and network controls that escalate with contract risk.
⚡ TL;DR: Pool = shared tables + enforced tenant predicates + app authz; Bridge = per-tenant KMS CMK + IAM path + noisy-neighbor limits; Silo = dedicated account/VPC/cell. Encode the tier in provisioning, not wiki tribal knowledge. Pair with Cell-Based Architecture on AWS and Bedrock Retrieval Filters: Tenant Isolation.
Tier definitions that survive sales calls
| Tier | Compute | Data | Keys | When |
|---|---|---|---|---|
| Pool | Shared ECS/Lambda | Shared table, tenant_id |
Account CMK | SMB / free |
| Bridge | Shared + reserved concurrency | Shared table + per-tenant S3 prefix | Per-tenant CMK | Mid-market |
| Silo | Dedicated cell/account | Dedicated DB/bucket | Tenant-owned CMK | Enterprise / regulated |
// ✅ Central guard — every data access carries TenantContext
export type Tier = "pool" | "bridge" | "silo";
export interface TenantContext {
tenantId: string;
tier: Tier;
kmsKeyArn: string;
}
export function assertTenantRow(row: { tenant_id: string }, ctx: TenantContext) {
if (row.tenant_id !== ctx.tenantId) {
throw new Error("tenant_isolation_violation"); // ✅ fail closed
}
}
IAM and KMS for bridge tier
// ✅ S3 prefix isolation with session tags / inline session policy
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::saas-data/${aws:PrincipalTag/tenant_id}/*"
}
# ✅ Encrypt with per-tenant CMK; never default to account key for bridge+
import boto3
s3 = boto3.client("s3")
def put_object(ctx, key: str, body: bytes) -> None:
s3.put_object(
Bucket="saas-data",
Key=f"{ctx.tenant_id}/{key}",
Body=body,
ServerSideEncryption="aws:kms",
SSEKMSKeyId=ctx.kms_key_arn, # ✅ tenant CMK
)
❌ Relying only on application filters without IAM/KMS boundaries for bridge/silo — one SQLi-shaped bug becomes a multi-tenant breach.
Noisy neighbors and blast radius
Pool tiers still need bulkheads: per-tenant rate limits (Multi-Tenant Rate Limits), reserved Lambda concurrency, and cell placement so a silo outage does not take pool customers.
Closing checklist
✅ Dos
– ✅ Document tier → controls matrix in an ADR
– ✅ Provision KMS keys and IAM paths automatically on upgrade
– ✅ Fail closed on tenant mismatch
– ✅ Load-test noisy-neighbor controls per tier
– ✅ Align contracts/DPA with actual technical tier
❌ Don’ts
– ❌ Don’t sell “dedicated” while sharing CMKs
– ❌ Don’t skip integration tests that attempt cross-tenant reads
– ❌ Don’t put all enterprise silos in one cell
– ❌ Don’t let support tooling bypass tenant guards without audit
Related reading
- Cell-Based Architecture on AWS: Limit Blast Radius Across SaaS Tenants
- Multi-Tenant Rate Limits: Redis Cluster Token Buckets
- Bedrock Retrieval Filters: Tenant Isolation for Multi-SaaS Code RAG
- Multi-Tenant Coding Assistants: Isolated ECS Fargate Spot Runtimes
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
