Lion Cipher Lab

Prove tenant isolation in a multi-tenant SaaS -- nine layers, one harness, zero leaks

Why it matters

Every SaaS company on earth tells its customers that their data is isolated from the other customers. Almost none of them can prove it, and the reason is structural rather than lazy: tenant isolation is not one control, it is eight or nine independent boundaries stacked on top of each other, and it takes exactly one of them to leak. Your database has a tenant_id column on every table and you feel good about it -- but the object store keys the files by a prefix, the response cache keys them by resource id, the search index is a second copy of the data with its own query path, the nightly…

What you'll learn

Steps in this lab

  1. [0] What tenant isolation actually is, and why nobody can prove theirs
  2. [0a] Set up -- open a terminal and check Python (assume nothing)
  3. [0b] The isolation boundary, written out as nine layers
  4. [0c] Create tenantapp.py -- the multi-tenant application, in four builds
  5. [0d] The nine defects, one per layer -- read them before you run anything
  6. [0e] Create isoharness.py -- the actual deliverable
  7. [0f] Create runlab.py -- the driver that runs all fifteen experiments
  8. [1] The size of the question: how big is the isolation matrix?
  9. [2] The test suite you already have is green
  10. [3] The full harness against the vulnerable build (cold cache)
  11. [4] The same harness, the same build, a warm cache -- and why CI never catches this
  12. [5] The AND/OR precedence bug, read at the query-planner level
  13. [6] The aggregate oracle -- leak a number, recover a string
  14. [7] The 404-versus-403 existence oracle
  15. [8] IDOR on an opaque uuid -- 122 bits of entropy, 0 bits of guessing
  16. [8a] The three layers people forget: the worker, the logs and the counters
  17. [9] The architectural fix, part 1: a data accessor you cannot misuse
  18. [9a] The architectural fix, parts 2 and 3: a key TYPE and a deny-by-default resolver
  19. [9b] The static audit -- and the two reasons grep is not a control
  20. [10] The hardened build: same harness, same 648 probes
  21. [11] Control failure mode 1: the fix that scores zero leaks and breaks the product
  22. [12] Control failure mode 2: a harness that only tests reads
  23. [13] The real test of an architectural control: let a careless developer add an endpoint
  24. [14] Control failure mode 3: a fixture whose tenants are all the same shape
  25. [15] Where Postgres row-level security is the right answer -- and what it does not cover
  26. [16] The tenancy spectrum, priced honestly
  27. [17] Named failure modes of the CONTROLS, each with the number you measured
  28. [18] Wire it into CI -- the part that makes it a control instead of an exercise
  29. [19] When NOT to do this, and how a professional does it differently
  30. [20] Where to go next (all optional, none required)

Open the full interactive lab →