Aller au contenu

Multi-tenant isolation

Status: Placeholder — to be developed. Last reviewed:Reference structural sibling: guidelines/i18n/translation-rules.md (single hard rule + workflow + antipatterns style — not component-style).

Scope (when this guideline lands)

How practice-scoping works and the rules that prevent data leaks across practices: UserPracticeAccess model, the active-practice context (session? middleware? URL?), required queryset filtering on every view that reads practice-scoped data, how new models declare their practice FK, audit checklist for any view that does not filter by practice.

This is high blast radius — a missed filter exposes another practice's patient data. Of the unwritten guidelines, this is the one with the most "if we don't enforce this, real harm happens" weight.

Out of scope (cross-refs)

  • Authentication / login flow / view-level access (which views a user can REACH) → guidelines/security/auth-and-permissions.md (placeholder). This file covers WHICH ROWS a user can see, given they've reached the view.
  • Data-content sensitivity / logging redactionguidelines/security/pii-and-logging.md (placeholder).
  • Admin practice scoping (the demoted superuser-only admin) → guidelines/backend/admin.md (placeholder).
  • Model FK declaration patternsguidelines/backend/models.md (placeholder); this file owns the which models need a practice FK policy.

Sources to mine when writing this

  • apps/accounts/models.py — custom User, UserPracticeAccess model and its enforcement points.
  • apps/accounts/middleware.py (or wherever the active-practice context lives) — how the request knows the current practice.
  • apps/core/mixins.pyMultiPracticeFilterMixin and any other practice-scoping mixins.
  • Existing list/detail views — enumerate the queryset-filtering pattern (mixin? base view? manual?).
  • Models with a practice FK vs models without one — document the rule for which is which.
  • Any past data-leak incident or near-miss (check roadmap/done/ for security-related items; check git log --all --grep="leak\|cross-practice\|isolation").

Starter hard rules to investigate

  1. Every queryset on a practice-scoped model MUST filter by current practice. Period. No exceptions for "internal" views.
  2. Filtering is mixin-driven, not manual. Use MultiPracticeFilterMixin (or whatever the canonical mixin is) on every view. Manual .filter(practice=request.user.practice) is a smell — easy to forget.
  3. Every new model declares whether it's practice-scoped. Either has a practice FK or has a comment explaining why it's global.
  4. Cross-practice operations (e.g. group-level reports) require explicit MultiPracticeFilterMixin config or an admin-only view.
  5. The active practice is set once per request by middleware, not derived ad-hoc in views.

Decision points to settle

  1. Where the active practice context lives: session? middleware-attached request attribute? URL? Pick one canonical mechanism.
  2. What counts as practice-scoped: directly (practice FK) vs transitively (patient.practice via FK chain). Define the rule.
  3. Fail-open vs fail-closed: when the active practice is unset (e.g. mid-login), do queries return [] or raise? Pick fail-closed.
  4. Direct-URL access to a record outside the user's practice scope: 404 (don't reveal existence) or 403 (acknowledge but deny)? Coordinate with ux/navigation.md.
  5. Multi-practice users (group admins): how is the "active" practice picked when a user has access to several?

Known deviations to look for during writing

  • Views with Model.objects.all() or Model.objects.filter(...) without practice scoping where the model has a practice FK.
  • Models with a practice FK that's null=True for unclear reasons (potential cross-practice records).
  • Admin queries that return all practices regardless of user.
  • API endpoints in apps/websites/ — how do they handle multi-tenant scoping vs the website views' mixin pattern?

If found, file IMMEDIATELY as a backlog item with Area: security | Effort: ? — do NOT wait for the guideline to land. Cross-practice leaks are P0.