I used to think "bulletproof" was a design style. I thought it meant clean lines, strict types, and a schema that looked perfect on a whiteboard.
I was wrong.
Recently, I walked into a design review for a multi-tenant SaaS platform. I slammed my schema on the table and called it "bulletproof."
What followed was not a pat on the back. It was a dissection. My "perfect" architecture was torn apart, layer by layer, until I was forced to admit a hard truth: A fortress built on assumptions is just a paper wall.
This is the story of that review. It’s a breakdown of the critical flaws in "modern" SaaS database design—specifically regarding multi-tenancy, Row Level Security (RLS), and privilege escalation—and the engineering doctrine that emerged from the ashes.
1. The Multi-Tenancy Trap: "Shared" Means "Leaking"
The first casualty of the review was my multi-tenant strategy. I had opted for the standard SaaS pattern: a shared database, shared schema, and RLS to keeping everything separate.
The Critic’s Verdict:
The Reality Check:
It is tempting to dismiss this as paranoia. After all, shared tables are the industry standard. But the critique cut deeper: Isolation based purely on application logic is fragile.
If you rely solely on a WHERE tenant_id = X clause injected by a helper function, you are one typo away from a massive data breach.
The Lesson:
Shared-table tenancy is acceptable only when tenant lineage is enforced by the database engine itself. This means:
- Foreign Keys everywhere: Every child table must carry the
tenant_idand validate it against its parent. - Composite Primary Keys: Include
tenant_idin your PKs to physically cluster data and make "leaking" structurally impossible. - Partitioning: Without it, you aren't just risking security; you're creating a "performance crater" where one noisy neighbor kills the platform.
2. The Backdoor You Built Yourself: SECURITY DEFINER
I was proud of my helper functions. To make the DX (Developer Experience) smooth, I wrapped complex logic in SECURITY DEFINER functions, granting them elevated privileges to handle sensitive tasks.
The Critic’s Verdict:
The Reality Check:
This was the most humbling moment. I deemed the system secure because I had "permissions." But I had ignored the execution context. In PostgreSQL, if you don't explicitly lock down the search_path, a function running with admin privileges might accidentally execute a script planted by a user in the public schema.
The Lesson:
"Security" isn't about having functions; it's about proving ownership.
- Lock the search_path: firmly set it to trusted schemas only.
- Revoke Public Creation: Never let the
publicschema be writable. - CI Verification: Use regex and static analysis in your CI pipeline to reject any
SECURITY DEFINERfunction that doesn't explicitly sanitize its environment.
3. Performance is a Constraint, Not a "ToDo"
I had leaned heavily on RLS and JSONB. "It gives us flexibility," I argued. "We can iterate faster."
The Critic’s Verdict:
The Reality Check:
Row Level Security is magical, until you run EXPLAIN ANALYZE. If your RLS policy calls a complex function, the database runs that function for every single row it scans. My "flexible" schema was effectively performing a denial-of-service attack on itself.
Similarly, my overuse of JSONB for "future-proofing" was lazy. The query planner cannot accurately estimate selectivity inside a JSON blob, leading to garbage execution plans.
The Lesson:
- RLS must be planner-friendly: Use simple, inlinable checks.
- Falsifiable Performance: If
EXPLAINshows a standard user query hitting aSeq Scan, the build should fail. Performance is not an optimization; it is a correctness requirement.
4. The Definition of Engineering
The argument ended when I stopped defending my "intentions" and started committing to "proofs."
My Pivot:
The Resolution:
This is the core takeaway.
Conclusion
Bulletproof is not a design style. It is not an aesthetic. It is a property proven by constraints, ownership checks, query plans, and automated tests.
If your Continuous Integration (CI) pipeline cannot falsify your claim—if it cannot prove that a tenant is isolated, or that a query uses an index—then your architecture is not bulletproof.
It’s just a drawing of a fortress. And paper walls don't stop bullets.
