diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index 14cf3a6..14ca699 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -1,17 +1,18 @@ @@ -43,58 +44,72 @@ ### Tenant Isolation is Non-negotiable - Every read/write MUST be tenant-scoped. - Cross-tenant views (MSP/Platform) MUST be explicit, access-checked, and aggregation-based (no ID-based shortcuts). - Prefer least-privilege roles/scopes; surface warnings when higher privileges are selected. -- A non-member attempting to access a tenant route MUST be deny-as-not-found (404). +- Tenant membership is an isolation boundary. If the actor is not entitled to the tenant scope, the system MUST respond as + deny-as-not-found (404). -### RBAC Standard (RBAC-001) +### RBAC & UI Enforcement Standards (RBAC-UX) -RBAC-001 Two Planes +RBAC Context — Planes, Roles, and Auditability - The platform MUST maintain two strictly separated authorization planes: - Tenant plane (`/admin/t/{tenant}`): authenticated Entra users (`users`), authorization is tenant-scoped. - Platform plane (`/system`): authenticated platform users (`platform_users`), authorization is platform-scoped. - Cross-plane access MUST be deny-as-not-found (404) (not 403) to avoid route enumeration. - -RBAC-002 Capabilities-first Authorization -- Feature code MUST NOT check raw roles directly (e.g. string role comparisons). -- Feature code MUST check capabilities via Gates/Policies only. -- A canonical capability registry MUST exist as the single source of truth (e.g. `TenantCapabilities` / `PlatformCapabilities`). -- Role → capability mapping MUST reference only registry entries. - -RBAC-003 Least Privilege Role Semantics -- Tenant roles MUST follow least-privilege semantics: +- Tenant role semantics MUST remain least-privilege: - Readonly: view-only; MUST NOT start operations and MUST NOT mutate data. - Operator: MAY start allowed tenant operations; MUST NOT manage credentials, settings, members, or perform destructive actions. - Manager: MAY manage tenant configuration and start operations; MUST NOT manage tenant memberships (Owner-only). - Owner: MAY manage memberships and all tenant configuration; Owner-only “danger zone” actions MUST remain Owner-only. - -RBAC-004 UI is not Security -- Hiding UI elements is NOT sufficient. -- Every mutation endpoint and action MUST enforce authorization server-side (Policy/Gate). - -RBAC-005 Destructive Actions Gate -- All destructive actions (delete / force delete / irreversible operations) MUST: - - require an explicit confirmation (e.g., `requiresConfirmation()` or equivalent), - - be protected by a Policy/Gate, - - have at least one regression test asserting the action is forbidden for non-authorized roles. - -RBAC-006 Membership Safety Rule - The system MUST prevent removing or demoting the last remaining Owner of a tenant. +- All access-control relevant changes MUST write `AuditLog` entries with stable action IDs, and MUST be redacted (no secrets). -RBAC-007 Tenant Isolation -- All tenant-plane queries MUST be tenant-scoped. -- A non-member attempting to access a tenant route MUST be deny-as-not-found (404). +RBAC-UX-001 — Server-side is the source of truth +- UI visibility / disabled state is never a security boundary. +- Every mutating action (create/update/delete/restore/archive/force-delete), every operation start, and every credential/ + config change MUST enforce authorization server-side via `Gate::authorize(...)` or a Policy method. +- Any missing server-side authorization is a P0 security bug. -RBAC-008 Auditing -- All access-control relevant changes MUST write `AuditLog` entries with stable action IDs, including: - - membership add / role change / remove - - provider credential rotation / connection disable - - break-glass enter / exit / expire (platform plane) -- `AuditLog` entries MUST be redacted (no secrets/tokens, minimal identity fields). +RBAC-UX-002 — Deny-as-not-found for non-members +- Tenant membership (and plane membership) is an isolation boundary. +- If the current actor is not a member of the current tenant (or otherwise not entitled to the tenant scope), the system MUST + respond as 404 (deny-as-not-found) for tenant-scoped routes/actions/resources. +- This applies to Filament resources/pages under tenant routing (`/admin/t/{tenant}/...`), Global Search results, and all + action endpoints (Livewire calls included). -RBAC-009 Testability Gate -- Any new feature that introduces or changes authorization MUST include: - - at least one positive test (authorized user can do it), - - at least one negative test (unauthorized user cannot do it), - - and MUST NOT introduce role-string checks outside the central mapping/registry. +RBAC-UX-003 — Capability denial is 403 (after membership is established) +- Within an established tenant scope, missing permissions are authorization failures. +- If the actor is a tenant member, but lacks the required capability for an action, the server MUST fail with 403. +- The UI may render disabled actions, but the server MUST still enforce 403 on execution. + +RBAC-UX-004 — Visible vs disabled UX rule +- For tenant members: actions SHOULD be visible but disabled when capability is missing. +- Disabled actions MUST provide helper text explaining the missing permission. +- For non-members: actions MUST behave as not found (404) and SHOULD NOT leak resource existence. +- Exception: highly sensitive controls (e.g., credential rotation) MAY be hidden even for members without permission. + +RBAC-UX-005 — Destructive confirmation standard +- All destructive-like actions MUST require confirmation. +- Delete/force-delete/archive/restore/remove membership/role downgrade/credential rotation/break-glass enter/exit MUST use + `->requiresConfirmation()` and SHOULD include clear warning text. +- Confirmation is UX only; authorization still MUST be server-side. + +RBAC-UX-006 — Capability registry is canonical +- Capabilities MUST be centrally defined in a single canonical registry (constants/enum). +- Feature code MUST reference capabilities only via the registry (no raw string literals). +- Role → capability mapping MUST reference only registry entries. +- CI MUST fail if unknown/unregistered capabilities are used. + +RBAC-UX-007 — Global search must be tenant-safe +- Global search results MUST be scoped to the current tenant. +- Non-members MUST never learn about resources in other tenants (no results, no hints). +- If a result exists but is not accessible, it MUST be treated as not found (404 semantics). + +RBAC-UX-008 — Regression guards are mandatory +- The repo MUST include RBAC regression tests asserting at least: + - Readonly cannot mutate or start operations. + - Operator can run allowed operations but cannot manage configuration. + - Manager/Owner behave according to the role matrix. +- The repo SHOULD include an automated “no ad-hoc authorization” guard that blocks new status/permission mappings sprinkled + across `app/Filament/**`, pushing patterns into central helpers. ### Operations / Run Observability Standard - Every long-running or operationally relevant action MUST be observable, deduplicated, and auditable via Monitoring → Operations. @@ -159,4 +174,4 @@ ### Versioning Policy (SemVer) - **MINOR**: new principle/section or materially expanded guidance. - **MAJOR**: removing/redefining principles in a backward-incompatible way. -**Version**: 1.5.0 | **Ratified**: 2026-01-03 | **Last Amended**: 2026-01-27 +**Version**: 1.6.0 | **Ratified**: 2026-01-03 | **Last Amended**: 2026-01-28 diff --git a/.specify/templates/plan-template.md b/.specify/templates/plan-template.md index f3bc22c..3a9e510 100644 --- a/.specify/templates/plan-template.md +++ b/.specify/templates/plan-template.md @@ -35,7 +35,9 @@ ## Constitution Check - Read/write separation: any writes require preview + confirmation + audit + tests - Graph contract path: Graph calls only via `GraphClientInterface` + `config/graph_contracts.php` - Deterministic capabilities: capability derivation is testable (snapshot/golden tests) -- RBAC Standard: two planes (/admin vs /system) remain separated; cross-plane is 404; authorization checks use Gates/Policies + capability registries (no role-string checks) +- RBAC-UX: two planes (/admin vs /system) remain separated; cross-plane is 404; non-member tenant access is 404; member-but-missing-capability is 403; authorization checks use Gates/Policies + capability registries (no raw strings, no role-string checks) +- RBAC-UX: destructive-like actions require `->requiresConfirmation()` and clear warning text +- RBAC-UX: global search is tenant-scoped; non-members get no hints; inaccessible results are treated as not found (404 semantics) - Tenant isolation: all reads/writes tenant-scoped; cross-tenant views are explicit and access-checked - Run observability: long-running/remote/queued work creates/reuses `OperationRun`; start surfaces enqueue-only; Monitoring is DB-only; DB-only <2s actions may skip runs but security-relevant ones still audit-log; auth handshake exception OPS-EX-AUTH-001 allows synchronous outbound HTTP on `/auth/*` without `OperationRun` - Automation: queued/scheduled ops use locks + idempotency; handle 429/503 with backoff+jitter diff --git a/.specify/templates/spec-template.md b/.specify/templates/spec-template.md index c2bec19..1ae7c69 100644 --- a/.specify/templates/spec-template.md +++ b/.specify/templates/spec-template.md @@ -82,12 +82,17 @@ ## Requirements *(mandatory)* (preview/confirmation/audit), tenant isolation, run observability (`OperationRun` type/identity/visibility), and tests. If security-relevant DB-only actions intentionally skip `OperationRun`, the spec MUST describe `AuditLog` entries. -**Constitution alignment (RBAC Standard):** If this feature introduces or changes authorization behavior, the spec MUST: +**Constitution alignment (RBAC-UX):** If this feature introduces or changes authorization behavior, the spec MUST: - state which authorization plane(s) are involved (tenant `/admin/t/{tenant}` vs platform `/system`), - ensure any cross-plane access is deny-as-not-found (404), -- describe how authorization is enforced server-side (Gates/Policies), -- reference the canonical capability registry (no role-string checks in feature code), -- include at least one positive and one negative authorization test. +- explicitly define 404 vs 403 semantics: + - non-member / not entitled to tenant scope → 404 (deny-as-not-found) + - member but missing capability → 403 +- describe how authorization is enforced server-side (Gates/Policies) for every mutation/operation-start/credential change, +- reference the canonical capability registry (no raw capability strings; no role-string checks in feature code), +- ensure global search is tenant-scoped and non-member-safe (no hints; inaccessible results treated as 404 semantics), +- ensure destructive-like actions require confirmation (`->requiresConfirmation()`), +- include at least one positive and one negative authorization test, and note any RBAC regression tests added/updated. **Constitution alignment (OPS-EX-AUTH-001):** OIDC/SAML login handshakes may perform synchronous outbound HTTP (e.g., token exchange) on `/auth/*` endpoints without an `OperationRun`. This MUST NOT be used for Monitoring/Operations pages. diff --git a/.specify/templates/tasks-template.md b/.specify/templates/tasks-template.md index afbf92f..63d9c10 100644 --- a/.specify/templates/tasks-template.md +++ b/.specify/templates/tasks-template.md @@ -16,7 +16,12 @@ # Tasks: [FEATURE NAME] without an `OperationRun`. **RBAC**: If this feature introduces or changes authorization, tasks MUST include: - explicit Gate/Policy enforcement for all mutation endpoints/actions, -- capability registry usage (no role-string checks in feature code), +- explicit 404 vs 403 semantics: + - non-member / not entitled to tenant scope → 404 (deny-as-not-found) + - member but missing capability → 403, +- capability registry usage (no raw capability strings; no role-string checks in feature code), +- tenant-safe global search scoping (no hints; inaccessible results treated as 404 semantics), +- destructive-like actions use `->requiresConfirmation()` (authorization still server-side), - cross-plane deny-as-not-found (404) checks where applicable, - at least one positive + one negative authorization test. **Badges**: If this feature changes status-like badge semantics, tasks MUST use `BadgeCatalog` / `BadgeRenderer` (BADGE-001),