the-custodian/research/2026-08-17-adr008-plane-a-authorization.md
codex 04f888399f research: external survey of multi-tenancy practice, one digest per ADR-008 plane
Four search-and-read passes against AWS Well-Architected SaaS Lens, Azure
Architecture Center, OWASP, the OpenID Foundation, and PostgreSQL practitioner
literature. Each digest holds one of our ladders against outside practice and
lists its sources.

Headline: the draft-1 -> draft-2 reframe is corroborated. Microsoft states
"isolation is a spectrum, not a discrete property" and derives our E/P coupling
independently. The shape is sound; the details are not.

Five corrections draft-2 needs:

- E3 overclaims. Any session can re-SET the GUC, so RLS is strong against a
  forgotten predicate and useless against SQL injection or a compromised
  process. The ADR says "the application cannot trivially route around" - the
  exact overclaim it prohibits elsewhere.
- The pooling claim in section 16 is backwards. Transaction-scoped SET LOCAL is
  what makes E3 safe under pooling; statement-level pooling is what serves
  other tenants' rows, only under production concurrency.
- E3 layers on E2 rather than superseding it.
- Placement cannot be expressed per tenant, but the commercial section requires
  exactly that - a tier bought by some tenants puts a service at two P levels
  at once, which the posture vector cannot represent.
- FORCE ROW LEVEL SECURITY is mandatory: ADR-0001 already established that our
  migration role owns the tables it creates, so it would silently bypass every
  policy.

Also: pgTAP and pgrls make the E3 evidence artifact far cheaper than assumed;
OWASP publishes a Multi-Tenant Security Cheat Sheet we reference nowhere; our
tenant-boundary gap is BOLA, OWASP API1, top of that list since it launched;
AuthZEN 1.0 went Final in January and flex-auth is pre-standard; and no plane
covers tenant offboarding, which OWASP makes a MUST.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 13:00:02 +02:00

14 KiB
Raw Permalink Blame History

Plane A — Authorization: prior knowledge and best practices

Research artefact — 2026-08-17. Supports canon/architecture/adr-008-multi-tenancy-model.md (draft-2), plane A of four. Surveys external practice for binding a request to the tenants it may act for, and holds our A0A3 ladder against it. Companions: plane I (identity), plane E (enforcement), plane P (placement).

1. What the plane covers

How a request is bound to the tenants its principal may act for, where that decision is made, and how the decision point is kept honest. Our ladder:

Level State
A0 None, or tenant context not carried
A1 Ad-hoc checks scattered through handlers
A2 Single local authorization boundary; context bound once
A3 Delegated to flex-auth as PDP, live re-query where the IAM Profile requires

2. This is the industry's #1 vulnerability class, by name

The single most important external finding: what our framework calls a tenant boundary failure, the security industry calls Broken Object Level Authorization, and it is not a marginal concern.

  • BOLA is API1 in the OWASP API Security Top 10 and "has held the top spot since the list launched" (Decryption Digest — BOLA and IDOR testing guide).
  • It is "consistently the most common, the most exploitable, and the most damaging API vulnerability across bug bounty submissions, public disclosures, and production penetration tests" (ibid.).
  • The multi-tenant framing is explicit: "a single missing authorization check can let one tenant access another's storage buckets, database records, or configuration data" (Wiz — OWASP API security top 10).

Against our model: ADR-008 §13.2 identifies the missing cross-tenant evidence artifact as the framework's largest gap. This research upgrades that from an internal judgement to an industry consensus: we have no test coverage for the vulnerability class that ranks first globally. The ADR should cite BOLA/API1 by name so the gap is legible to anyone who arrives with a security background rather than an architecture one.

3. OWASP has a Multi-Tenant Security Cheat Sheet, and we should adopt it wholesale

This is the most directly applicable artefact found in the whole research effort, and neither ADR-008 nor any repo document references it (OWASP Multi-Tenant Security Cheat Sheet).

Its normative requirements, mapped to our ladders:

OWASP requirement Our plane Status
"Never trust client-supplied tenant IDs without validation" — derive from verified JWT claims I2 Implied, not stated. Tighten.
"Establish tenant context early in the request lifecycle (middleware/interceptor)"; bind to the authenticated session A2 Matches A2 exactly.
"Always validate that requested resources belong to the current tenant" E1E2 Matches.
"Include tenant context in all log entries" Not covered by any plane.
Tenant-isolated audit trails audit-core's concern; not in the framework.
"Ensure complete data deletion on tenant offboarding" Not covered. See §6.
Prefix all cache keys with tenant identifiers Not covered (see plane I §3).
Per-tenant rate limiting with tier-based quotas Adjacent to tenant-engine guardrails.
Separate API keys per tenant E4-adjacent Our E4 is credential-based; consistent.

Explicitly rejected anti-patterns, two of which we should check ourselves against:

  • "Skipping tenant validation for internal services." Our estate is mostly internal service-to-service calls — flex-auth calls tenant-engine synchronously on the authorization path, user-engine calls it for tenant creation. Our A ladder describes a request arriving with a subject; it does not say whether a service identity calling on behalf of a tenant carries and revalidates tenant context. This is a real question for A3.
  • "Exposing sequential or guessable resource IDs." Not assessed anywhere.

Proposed action: reference the cheat sheet from ADR-008 as the external baseline, and fold its uncovered requirements (logging, cache keys, offboarding deletion) into either the ladders or an explicit scope exclusion.

4. The PDP/PEP split is right, and there is now a standard for the wire

Our A3 delegates decisions to flex-auth as PDP, which is the mainstream architecture — a Policy Enforcement Point intercepts an action and queries a Policy Decision Point (DEV — RBAC vs ABAC vs ReBAC).

What is new since our contracts were written:

Against our model: flex-auth uses a bespoke CheckRequest shape and a bespoke action vocabulary (tenant.update, tenant.guardrail.set, …), with action strings copied verbatim between repos to avoid re-derivation. That coupling is exactly what AuthZEN standardises away. We are not wrong — we are pre-standard, and the standard landed seven months ago.

Proposed amendment: A3 currently says "delegated to flex-auth as PDP". Consider distinguishing:

  • A3 — externalised to a PDP (any protocol).
  • A4 — externalised over a standard interface (AuthZEN), so the PDP is swappable and the PEP is not coupled to one engine's request shape.

That gives the ladder somewhere to go and makes the current coupling visible rather than invisible. It also reframes key-cape/Keycloak's AuthZEN support as a fleet opportunity rather than an unrelated upstream feature.

5. Model choice: our vocabulary is RBAC-shaped, our problem may be ReBAC-shaped

  • ReBAC originates in Google's Zanzibar, modelling authorization as a graph of typed relationships, with authorization data stored as explicit tuples in a dedicated datastore (AuthZed — introduction to Google Zanzibar).
  • It is described as "well-suited to multi-tenant enterprise environments where access patterns are relational rather than role-based", and specifically for modelling "complex customer organizational structures while maintaining strict data isolation" (ibid., AuthZed — ABAC vs ReBAC).
  • Open implementations: SpiceDB, OpenFGA, Authzed (IAM Day by Day — Zanzibar & ReBAC).

Against our model: our capability roles (PLTF/IAM/VEN/CUS) are tenant-level RBAC, and user-engine owns memberships separately. The moment a question like "may this user act for this tenant, given they are a member of an organisation that administers it" arises, that is a relationship query, and role-flattening will start to strain. Not a present problem — flagged because the estate's own tenant taxonomy (family, community, association) is overtly relational and will produce these questions.

6. Finding: nothing in the framework covers tenant exit

OWASP lists "ensure complete data deletion on tenant offboarding" as a must (OWASP Multi-Tenant Security Cheat Sheet).

Our position today: tenant-engine's lifecycle contract provides reversible retirement and states plainly that there is no hard-delete. That is a defensible design for an audit-bearing estate, but it means:

  • No plane in ADR-008 has anything to say about tenant exit, data deletion, or the interaction with retention.
  • Physical backups on platform-pg retain 30 days, so even a hypothetical hard-delete leaves tenant data recoverable for a month — which is a GDPR erasure question, not just an architecture one.
  • rapp-postgres SCOPE explicitly excludes tenant identity, and tenant-engine explicitly excludes storage. Deletion falls between them.

Proposed action: this is a fifth concern the framework does not model, and it is not obviously a fifth plane — it is a lifecycle stage cutting across all four. Recommend adding it as an explicit open question rather than inventing a plane for it. The estate's gdpr_report tooling in the hub suggests someone has thought about this elsewhere; worth locating before drafting.

7. Finding: automated conformance testing may not be sufficient for this plane

This challenges the recommendation I gave in the ADR review (§18.3, "build a fleet conformance suite").

  • "Finding cross-tenant vulnerabilities requires semantic understanding of what data belongs to which context, and that understanding requires a human tester who can set up separate tenant contexts and compare the responses."
  • "Across 119 SaaS assessments, the highest-impact findings were concentrated in access control, business logic, and tenant isolation, precisely the categories that require context-aware human testing rather than automated tool execution." (Bugstrix — multi-tenant SaaS security testing)

Against our model: this does not invalidate the conformance suite — a mechanical check that every tenant-owned table carries the key (E1), or that a session without the GUC reads nothing (E3), is precisely the kind of structural assertion automation does well. What it invalidates is the hope that a suite replaces adversarial review at E2, where the boundary lives in business logic and the failure is semantic.

Proposed amendment to §13: split the evidence artifacts by what can be automated. E1 and E3 are mechanical and belong in CI. E2 is semantic and needs a periodic adversarial review with a recorded date, closer to a pen-test cadence than a test suite. Claiming E2 on the strength of a green CI run would be exactly the overclaim §6 of the ADR prohibits.

8. Summary against our ladder

Finding Verdict
Tenant boundary failure = BOLA = OWASP API1, top of the list since launch Reframe. Cite by name; our largest gap is the industry's largest risk.
OWASP Multi-Tenant Security Cheat Sheet exists and is unreferenced Adopt. Closest external baseline to our framework.
A2 matches OWASP's "establish context early, bind to session" Aligned.
"Skipping tenant validation for internal services" is a named anti-pattern Check ourselves. Our estate is mostly internal service-to-service.
PDP/PEP split is mainstream Aligned.
AuthZEN 1.0 Final (Jan 2026) standardises the PDP wire; Keycloak experimental May 2026 Gap. flex-auth is pre-standard and bespoke. Propose A4.
ReBAC/Zanzibar suits relational tenant structures Watch. Our family/community/association groupings are relational.
Tenant offboarding deletion is a MUST we do not model Finding. No plane covers exit; interacts with 30-day backup retention.
Cross-tenant testing needs human semantic review, not only automation Amend §13. E1/E3 mechanical, E2 adversarial-review with a date.

Sources