Skip to content

docs(review): add multi-tenancy review criteria - #130

Merged
kyle-sexton merged 14 commits into
mainfrom
docs/review-multi-tenancy
Jul 16, 2026
Merged

docs(review): add multi-tenancy review criteria#130
kyle-sexton merged 14 commits into
mainfrom
docs/review-multi-tenancy

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Summary

New conventions/review/multi-tenancy.md — diff-time review bars for a multi-tenant (SaaS) system, organized around horizontal authorization: keeping an already-authenticated request confined to its own tenant. security.md owns the vertical boundary (authn, sanitization, injection); this file owns everything downstream of "we know who is calling."

Criteria (all diff-anchored; a cross-tenant read/write defaults to Critical):

  • Tenant-scoped data access — missing tenant predicate; fetch-by-id without ownership; conditional per-tenant key selection.
  • Tenant identity and trust — client-supplied tenant used for scoping; enumerable cross-tenant identifiers (BOLA).
  • Shared state and configuration — cache key omits the tenant; global flag driving a tenant decision.
  • Asynchronous work — tenant context lost across the queue/job/event boundary.
  • Fairness — unbounded tenant-triggerable work with no per-tenant quota (noisy neighbor).
  • Tenant lifecycle — offboarding that misses a data store; provisioning defaults.
  • Boundaries section delimiting ownership vs security.md / performance.md / concurrency.md / observability.md.

Registered in the review README (placed by security.md, away from the code-design row in #129 to avoid a README merge conflict between the two held PRs).

Sourcing

Verified against AWS Well-Architected SaaS Lens (isolation mindset), OWASP (API1:2023 Broken Object Level Authorization + Authorization Cheat Sheet), and the Azure multitenant architecture guides. Dropped non-diff-reviewable candidates (tenancy-model choice, capacity planning, cost metering) by design; per-tenant encryption kept as an explicitly conditional bar.

Test plan

  • Local lefthook pre-commit — typos, editorconfig, gitleaks, markdownlint pass
  • CI lychee will validate the source URLs
  • Held for your review (hybrid policy)

🤖 Generated with Claude Code

New conventions/review/multi-tenancy.md — horizontal-authorization bars
that keep an authenticated request confined to its own tenant, distinct
from security.md's vertical trust boundary. Diff-anchored criteria for
tenant-scoped data access, tenant-context trust, cache and config
isolation, async tenant propagation, per-tenant fairness, and
offboarding-deletion completeness, plus a boundary section delimiting
ownership against security/performance/concurrency/observability.

A cross-tenant read or write defaults to Critical. Criteria verified
against AWS SaaS Lens, OWASP (API1:2023 BOLA + Authorization Cheat
Sheet), and Azure multitenant architecture guidance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1x1KNkkjSe2CjbuTb4Gis
@claude

claude Bot commented Jul 15, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 0s —— View job


I'll analyze this and get back to you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1560bdaf1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread conventions/review/multi-tenancy.md Outdated
Comment thread conventions/review/multi-tenancy.md Outdated
Two review-bot corrections:
- The dedicated-infrastructure carve-out suppressed all bars; narrow it to
  the data-scoping predicate so lifecycle/offboarding stays in scope for
  silo tenants (a dedicated store still needs deletion coverage).
- Enumerable-identifier severity hinged on id shape; make the Critical
  case the missing/bypassed tenant check (owned above) and treat id
  opacity as defense-in-depth (Suggestion when the scope check is present).

Addresses the codex-connector findings on #130.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1x1KNkkjSe2CjbuTb4Gis
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 0s —— View job


I'll analyze this and get back to you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8f45eb492f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread conventions/review/multi-tenancy.md Outdated
Comment thread conventions/review/multi-tenancy.md Outdated
… boundary

Two review-bot corrections:
- The client-supplied-tenant bar condemned any client-derived tenant that
  governs scoping, which would reject a legitimate multi-tenant user
  selecting a tenant per request. Reframe the finding as the missing
  membership check: validated selection (principal authorized for the
  tenant, then context set) is fine.
- The Boundaries section claimed security.md owns same-tenant object-level
  authorization; it has no such bar. Stop misdirecting reviewers — state
  that general within-tenant object authorization is outside this file's
  scope, without asserting a false owner.

Addresses the codex-connector findings on #130.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1x1KNkkjSe2CjbuTb4Gis
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1s —— View job


I'll analyze this and get back to you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f3f390b55f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread conventions/review/multi-tenancy.md Outdated
The blanket "cross-tenant read/write is Critical" wording would reject a
legitimate, explicitly-authorized platform-admin or support flow that is
designed to span tenants. Add the carve-out for a path that carries its own
cross-tenant authorization and audit controls.

Addresses the codex-connector finding on #130.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1x1KNkkjSe2CjbuTb4Gis
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 0s —— View job


I'll analyze this and get back to you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6a99a1c76

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread conventions/review/multi-tenancy.md Outdated
The client-supplied-tenant and async-boundary bars both flagged a
tenant value used only for telemetry (routing/logging, correlation)
as an Important multi-tenancy finding — contradicting this file's own
Boundaries section, which already assigns trace/log tenant correlation
to observability.md and reserves this file for the scoping failure.

- multi-tenancy.md — drop the telemetry-only case from both bars;
  point to observability.md instead of restating its concern.
- observability.md — add the tenant-correlation-tag bar under
  Semantic conventions and correlation so the boundary this file
  already claims is actually backed by a criterion.

Addresses the codex-connector finding on #130.
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 0s —— View job


I'll analyze this and get back to you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 56c2b04bd5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread conventions/review/observability.md Outdated
The tenant-correlation-tag bar flagged metrics missing the tenant
identifier alongside logs and traces, contradicting this same file's
Bounded tag cardinality bar, which already carves unbounded identities
(user identities, raw URLs, request identities) out of metric tags and
into logs/traces. In a SaaS environment with many or unbounded
tenants, a raw tenant id on a metric tag is exactly that unbounded-
identity case and risks a cardinality explosion.

- observability.md — scope the bar to logs/traces; metrics correlate
  through their log/trace context or a bounded tenant grouping (tier,
  plan, region) instead of the raw id.

Addresses the chatgpt-codex-connector P2 finding on #130.
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 0s —— View job


I'll analyze this and get back to you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46afa4e80f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread conventions/review/multi-tenancy.md Outdated
…alidation

The client-supplied-tenant bar flagged any routing-only tenant hint as
merely Important, even when the routing decision itself reaches tenant
resources with no downstream authorization. A reverse-proxy or gateway
hint used only to pick a backend before authentication has no
authenticated principal to validate against at that ingress point —
the safety property is that the routed application still authorizes
the request for that tenant before touching data. Narrow the bar:
Important only when the routed destination performs its own tenant
check; Critical when a routing decision can reach tenant resources
with no such downstream validation.

Addresses the codex-connector finding on #130.
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 0s —— View job


I'll analyze this and get back to you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a1a12d24d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread conventions/review/multi-tenancy.md Outdated
The prior wording flagged legitimate cross-tenant admin/support/
background paths that correctly decrypt with the data tenant's key
rather than the requester's.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1x1KNkkjSe2CjbuTb4Gis
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1s —— View job


I'll analyze this and get back to you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1112ab6cad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread conventions/review/multi-tenancy.md Outdated
kyle-sexton and others added 2 commits July 15, 2026 23:27
…dedicated

The carve-out's `container` example made compute isolation alone sound
sufficient. A per-tenant container or app stamp can still query a
pooled database, cache, queue, or index; that shared store still needs
the tenant predicate. Key the carve-out off the data store itself
being tenant-dedicated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1x1KNkkjSe2CjbuTb4Gis
…ancy

# Conflicts:
#	conventions/review/observability.md
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1s —— View job


I'll analyze this and get back to you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83c5a00761

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread conventions/review/multi-tenancy.md Outdated
…ancy

# Conflicts:
#	conventions/review/observability.md
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1s —— View job


I'll analyze this and get back to you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 319c94357b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread conventions/review/multi-tenancy.md Outdated
kyle-sexton and others added 2 commits July 16, 2026 00:22
The client-supplied-tenant bar treated any `?? defaultTenant` fallback
as the same defect as an unauthorized client-controlled tenant value,
which would flag a legitimate pattern like `requestedTenant ??
principal.defaultTenant`. Narrow the criterion: only a fallback to a
global or another client-controlled value is the same defect. A
fallback to a tenant derived from the authenticated principal or
session is safe once membership is confirmed before the default
reaches data access — the missing check is the defect, not the
default itself.

Addresses the codex-connector finding on #130.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The provisioning-defaults bar capped onboarding that seeds a new
tenant with global-scope or another tenant's access at Important,
even though this file's own default treats a cross-tenant read or
write as Critical. A copied default role that carries cross-tenant
permissions is that same break, immediately exposing or letting the
new tenant mutate other tenants' data. Keep Important for genuinely
harmless copied defaults with no access implications; make an
access- or data-bearing copied default Critical, matching the file's
established severity convention.

Addresses the codex-connector finding on #130.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 29s —— View job


Review — docs(review): add multi-tenancy review criteria

  • Gather context (CLAUDE.md, changed files)
  • Read the PR diff and changed files
  • Review multi-tenancy criteria for correctness, completeness, and alignment
  • Post findings

The file is well-grounded in authoritative sources and the nine-round Codex iteration has resolved the major structural ambiguities (dedicated-infrastructure carve-out, BOLA severity, admin-path exceptions, routing-only tenant hints, server-derived defaults, metric cardinality). What follows is the residual signal.

Findings

1. Authoritative gap: within-tenant object-level authorization is deferred to nowhere

conventions/review/multi-tenancy.md line 38

"General within-tenant object-level authorization — one user reaching another's record inside the same tenant — is a broader authorization concern outside this file's scope."

security.md covers authentication, injection, secrets, and transport. It doesn't have IBAC/ABAC criteria. A reviewer following the Boundaries section expecting to find within-tenant ownership checking in security.md will find nothing there. Either add a one-liner to security.md covering per-resource ownership checks (OWASP BFLA / IDOR within a tenant), or amend this sentence to say "covered by security.md" only once that criterion actually exists there. As written, it creates a dead-end dereference in the review suite.

Fix this →


2. "Cross-tenant" used with two different meanings in the same file

conventions/review/multi-tenancy.md line 29

"Critical where the unbounded path enables a cross-tenant denial of service on a critical shared resource"

Everywhere else in the document "cross-tenant" means unauthorized data access (horizontal authorization). Here it means resource exhaustion affecting neighboring tenants — the noisy-neighbor problem. These are distinct failure modes. A reviewer will reasonably read "cross-tenant" as unauthorized access and be confused about why a quota issue is being described. The Azure source you link calls this the "noisy-neighbor antipattern," which is clearer language. Suggested phrasing: "Critical where the unbounded path causes noisy-neighbor degradation on a critical shared resource."

Fix this →


3. Forward reference in "Per-tenant key selection" requires reading out of order

conventions/review/multi-tenancy.md line 11

"the authorized target tenant on an explicitly cross-tenant admin, support, or background path — the same carve-out as the tenant-identity check below"

This requires the reader to understand a carve-out that isn't defined until the next section. The carve-out is already inlined via the phrase "authorized target tenant on an explicitly cross-tenant admin, support, or background path," so the forward reference adds no information — it just signals that something more exists below. Dropping ", the same carve-out as the tenant-identity check below" would make the sentence self-contained.


4. Synchronous cross-service tenant propagation is not covered

The "Asynchronous work" section covers queue/job/event/scheduled-task boundaries. It does not cover synchronous downstream service calls (an HTTP request or gRPC call to a downstream service that omits the tenant header or JWT claim). In a microservices deployment this is as common a propagation failure as the async case. The Boundaries section says concurrency.md owns "the mechanism of carrying ambient context across async boundaries" — but neither concurrency.md nor multi-tenancy.md addresses synchronous cross-process propagation.

This may be intentional if the repo primarily targets monolithic or single-service deployments, in which case a comment noting the scope limitation would preempt future confusion.


Minor

  • "Fetch-by-id without ownership" (line 10) is the sparsest bullet in the file — one sentence, no source, no conditional context. The relationship to the BOLA "enumerable cross-tenant identifiers" bullet below it is not immediately clear (both concern object-level access, but they're distinct: one is about the missing predicate, the other about id opacity). Adding a parenthetical like "(distinct from guessable-id defense-in-depth below)" would save a reviewer a re-read.

  • "Critical when an unvalidated value governs access" in the Client-supplied tenant bullet (line 15) — the severity is stated twice in the same sentence for two cases (Critical when governs access; Important when feeds routing). The sentence is already very long; consider a sub-list if future edits expand it further. Not a blocker.


What's working well

The silo vs. pool carve-out (line 5) was the thorniest structural problem and is now precise: dedicated data store = predicate redundant, dedicated compute only = predicate still required. The admin-path exception, server-derived default, and routing-hint carve-outs in the tenant identity section are all technically correct and narrowly scoped. The Boundaries section is the most explicit delineation of ownership across the review suite I've seen in this repo — the observability.md cross-reference is well-placed.

The README.md and observability.md touches are minimal and correct.


@kyle-sexton
kyle-sexton merged commit 1573367 into main Jul 16, 2026
39 checks passed
@kyle-sexton
kyle-sexton deleted the docs/review-multi-tenancy branch July 16, 2026 11:59
kyle-sexton added a commit that referenced this pull request Jul 16, 2026
## Summary

- Adds an **Object-level authorization** section to `security.md`
covering the same-tenant BOLA/IDOR case: a request that carries an
object id checked for well-formedness but not for the caller's
authorization over that specific record.
- Fills a gap surfaced during dogfood on #130: `multi-tenancy.md`'s own
Boundaries section explicitly scopes out "one user reaching another's
record inside the same tenant" as outside its file — `security.md` owned
no object-level/ownership authorization check to pick it up.
- Sourced from OWASP API1:2023 (Broken Object Level Authorization), the
OWASP Authorization Cheat Sheet, and CWE-639; adds `security.md`'s first
`## Sources` section.
- Cross-references `multi-tenancy.md`'s "Fetch-by-id without ownership"
bullet for the cross-tenant analog, using a code-span sibling ref (that
PR, #130, has not yet merged to `main` as of this branch).

## Test plan

- [x] `lefthook` pre-commit (typos, gitleaks, editorconfig,
markdownlint) — passed locally.
- [ ] CI green (markdown lint, lychee, review bot).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant