diff --git a/claude.md b/claude.md index 385a8ae..aa68f13 100644 --- a/claude.md +++ b/claude.md @@ -198,7 +198,14 @@ sitesvc also (re)declares the unique indexes on `users.email` and `orgs.slug` at - **Roles** — `owner`, `admin`, `member`. `/api/settings` and `/api/org/*` require owner or admin. - **Host/org guard** — `APP_ROOT_LABEL` (default `vantage`) defines the app root label. A request to `.vantage.` resolves that org from the slug and rejects sessions belonging to a different one. Org lookups are cached for 60s. -Unique indexes on user email and org slug are a **security property**, not an optimisation: `GetUserByEmail` does an unscoped `FindOne`, so duplicates would let the OIDC cross-org guard compare against an arbitrary user. Same for duplicate settings docs and duplicate ESO token hashes. +Unique indexes are a **security property**, not an optimisation. `users` is +unique on `(instance_id, email)` — one address is one user *within* an instance, +and the same address may hold a user in several instances, because an account's +people are projected into each instance they are granted. This is sufficient only +because **every lookup by email is scoped by instance**; there is deliberately no +unscoped lookup anywhere, and adding one would let the login path return an +arbitrary one of several matching users. Instance slug, settings instance and ESO +token hash remain globally unique. --- @@ -315,6 +322,7 @@ Notes that are not obvious from the structs: - `assignments.revoked_at: null` means active. Revocation is soft, preserving audit history. - `workflow_runs.steps_snapshot` freezes the resolved steps so editing the library never rewrites history. - `console_sessions.token_consumed_at` is set atomically to enforce one-time use. +- `users.auth_source` is `local`, `oidc` or `hq`. An `hq` user was projected from a Vantage HQ account and carries `hq_user_id`; HQ owns its role, password and existence. ### Migrations @@ -426,7 +434,7 @@ Windows: MSI built by CI (WiX), or `installer/setup.ps1` registering the agent a - AES-256-GCM at rest for private keys, key passphrases, vault secrets, OIDC client secrets, RDP/VNC credentials. - Console session tokens are one-time; RDP credentials are consumed on tunnel open. - ESO read token stored as a SHA-256 hash and rotatable. -- Unique indexes on user email, org slug, settings org, and ESO token hash are load-bearing for tenant isolation. +- Unique indexes on `(instance_id, email)`, instance slug, settings instance and the ESO token hash are load-bearing for tenant isolation. So is the absence of any unscoped lookup by email. - `authorized_keys` written `0600`, owned by root. The agent runs as root because it must. - Every mutating API path writes an audit event.