Skip to main content
Back to BlogEngineering

The Paper Fortress: Why 'Bulletproof' Architecture is a Lie (Until You Prove It)

A brutal post-mortem on multi-tenancy, Row Level Security, and the ego of software design. Why 'bulletproof' is a property proven by tests, not a design style.

John V. Akgul
January 17, 2026
5 min read

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:

"This isn’t bulletproof; it’s a ticking bomb... A bug in your RLS helpers could expose one agency’s contracts to another’s clients."

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_id and validate it against its parent.
  • Composite Primary Keys: Include tenant_id in 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:

"SECURITY DEFINER functions are a privilege escalation playground... If an attacker creates a malicious object in a writable schema, they can hijack your search_path and run code as the function owner."

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 public schema be writable.
  • CI Verification: Use regex and static analysis in your CI pipeline to reject any SECURITY DEFINER function 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:

"Your policies are performance killers... evaluated per row. Non-leakproof functions block index usage and force seq scans."

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 EXPLAIN shows a standard user query hitting a Seq 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:

"I’ll deliver a tight, executable pack with zero syntax errors, lock-safe migrations, and falsifiable tests."

The Resolution:

"Now we are finally doing engineering instead of arguing."

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.

Get Started

Make AI Your Edge.

Book a free AI assessment. We'll show you exactly which tools will save time, cut costs, and grow revenue — in weeks, not months.

Free 30-minute call. No commitment required.