Skip to content

Commit 952e3bb

Browse files
docs: add multi-tenancy enforcement rules to CLAUDE.md
Structural defense against tenant-leak regressions. Documents: - propertyId MUST be in WHERE alongside id filters (not just controllers) - propertyId must come from request, never derived (confused-deputy) - Documented exceptions: guests, properties, Connect API, internal receivers Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7c9fbc2 commit 952e3bb

1 file changed

Lines changed: 23 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,29 @@ Architecture: Option B — PMS is standalone, OTAIP agents connect via API (not
3636
- Tests required for all business logic
3737
- Use the webhook event pattern: entity.action (e.g., reservation.created)
3838

39+
## Multi-tenancy enforcement (non-negotiable)
40+
41+
Every service method that queries a table with `propertyId` MUST filter by it
42+
alongside any id filter. This applies to reads, updates, and deletes — even
43+
for methods called only internally, because future controllers may call them.
44+
45+
- `WHERE id = $1` on a property-scoped table is a BUG. Use
46+
`and(eq(table.id, id), eq(table.propertyId, propertyId))`.
47+
- Controllers: `propertyId` is a REQUIRED query param (UUID-validated) on every
48+
`:id` route, not optional. List endpoints also require it.
49+
- `propertyId` must come from the request, never inferred from other entity
50+
lookups (that creates a confused-deputy bug — the attacker supplies the id,
51+
the server derives a matching propertyId, scoping becomes a no-op).
52+
- Exceptions — document the reason in a code comment when you deviate:
53+
- `guests` table: cross-property by design (one person stays at multiple hotels)
54+
- `properties` table: the property IS the tenant
55+
- Connect API (`/api/v1/connect/*`): bearer-credential model via
56+
`confirmationNumber` — scoped by credential possession, not propertyId
57+
- Internal cron/webhook receivers invoked with trusted server-side ids
58+
59+
When adding a new controller route or service method that touches a
60+
property-scoped table, the WHERE clause is the first thing to verify.
61+
3962
## Project Structure
4063

4164
```

0 commit comments

Comments
 (0)