TenantAtlas/docs/audits/2026-03-09-enterprise-rbac-scope-audit.md
2026-03-09 11:39:36 +01:00

386 lines
29 KiB
Markdown

# Enterprise RBAC, Scope Enforcement & Navigation Governance Audit
**Datum:** 9. März 2026
**Anwendung:** TenantPilot / TenantAtlas
**Stack:** Laravel 12, Filament v5, Livewire v4, PostgreSQL, Tailwind v4
**Auditor:** Enterprise SaaS Security & Architecture Audit
**Scope:** RBAC, Multi-Tenancy, Scope Enforcement, Navigation Governance, Filament Authorization
---
## 1. Executive Security / Structure Assessment
### Gesamturteil: **Teilweise robust — mit gezieltem Hardening-Bedarf**
Die Anwendung verfügt über ein **architektonisch durchdachtes, capability-first RBAC-Modell** mit klarer Trennung von Platform/Workspace/Tenant-Scopes. Die Kernarchitektur (CapabilityResolver, UiEnforcement, Membership-Manager, Policies) ist solide und enterprise-geeignet.
Allerdings gibt es **strukturelle Lücken** in folgenden Bereichen:
- System-Panel-Widgets aggregieren cross-workspace/cross-tenant Daten ohne Kompartimentierung
- Einige Monitoring-Pages fehlen explizite `canAccess()` Checks und verlassen sich ausschließlich auf Middleware
- AuditLog-Modell hat keine `workspace_id` Spalte und damit kein strukturelles Workspace-Scoping
- Einige Modelle (OperationRun, BaselineProfile, AlertRule) haben keine Immutabilitäts-Traits am Model-Level
- URL-basierte Enumeration von sequenziellen IDs ist möglich (Finding, OperationRun)
### Die 5 kritischsten RBAC-/Scope-Probleme
| # | Problem | Severity |
|---|---------|----------|
| 1 | **System Panel leakt Cross-Workspace/Cross-Tenant Operational Data** — ControlTower-Widgets zeigen Workspace-Namen, Tenant-Namen und Fehlerstatistiken aller Mandanten an jeden PlatformUser | CRITICAL |
| 2 | **Directory-Pages exponieren alle Tenants/Workspaces** — System/Directory-Seiten erlauben Aufzählung aller registrierten Tenants/Workspaces mit Health-Status und Permission-Gaps | HIGH |
| 3 | **AuditLog hat kein strukturelles Workspace-Scoping** — Nur `tenant_id`, kein `workspace_id`; Scoping ist rein auf Query-Level und kann bei neuen Zugangspfaden umgangen werden | HIGH |
| 4 | **Monitoring-Pages (Operations, AuditLog) fehlen `canAccess()` Checks** — Verlassen sich nur auf Middleware + Query-Scoping, nicht auf explizite Page-Level Authorization | MEDIUM |
| 5 | **Sequenzielle IDs in URLs erlauben Enumeration** — Finding-IDs, OperationRun-IDs sind sequential integers in Deep Links und Notifications | MEDIUM |
### Die 5 wichtigsten Korrekturen
| # | Maßnahme | Priorität |
|---|----------|-----------|
| 1 | System-Panel Kompartimentierung: Capability-Granularisierung für cross-workspace Sicht | P0 |
| 2 | `canAccess()` auf allen Monitoring-Pages implementieren | P1 |
| 3 | AuditLog-Modell um `workspace_id` ergänzen mit Migration + Scoping-Trait | P1 |
| 4 | OperationRun + BaselineProfile + AuditLog mit DerivesWorkspaceId-Trait absichern | P2 |
| 5 | UUID-basierte Route-Keys für sensitive Ressourcen (Finding, OperationRun) evaluieren | P2 |
---
## 2. Access Model Map
### Subjekte
| Subjekt | Guard | Scope | Modell |
|---------|-------|-------|--------|
| **Platform User** | `platform` | System-weit | `PlatformUser` |
| **Workspace Owner** | `web` | Workspace | `User` + `WorkspaceMembership(role=owner)` |
| **Workspace Manager** | `web` | Workspace | `User` + `WorkspaceMembership(role=manager)` |
| **Workspace Operator** | `web` | Workspace | `User` + `WorkspaceMembership(role=operator)` |
| **Workspace Readonly** | `web` | Workspace | `User` + `WorkspaceMembership(role=readonly)` |
| **Tenant Owner** | `web` | Tenant | `User` + `TenantMembership(role=owner)` |
| **Tenant Manager** | `web` | Tenant | `User` + `TenantMembership(role=manager)` |
| **Tenant Operator** | `web` | Tenant | `User` + `TenantMembership(role=operator)` |
| **Tenant Readonly** | `web` | Tenant | `User` + `TenantMembership(role=readonly)` |
### Scopes
| Scope | Kontext | Enforcement |
|-------|---------|-------------|
| **Platform** | System-Administration (break-glass, directory, ops) | `platform` Guard + PlatformCapabilities |
| **Workspace** | Organisation/Account-Isolation | WorkspaceMembership + WorkspaceCapabilityResolver + WorkspaceContext (Session) |
| **Tenant** | Mandant innerhalb eines Workspace | TenantMembership + CapabilityResolver + Filament::getTenant() |
### Kernobjekte pro Scope
| Scope | Objekte |
|-------|---------|
| **Platform** | PlatformUser, Access Logs, Break-Glass-Sessions |
| **Workspace** | Workspace, WorkspaceMembership, WorkspaceSetting, AlertRule, AlertDestination, BaselineProfile, OperationRun (multi-tenant) |
| **Tenant** | Tenant, TenantMembership, Policy, PolicyVersion, BackupSet, BackupItem, BackupSchedule, RestoreRun, Finding, ReviewPack, InventoryItem, EntraGroup, ProviderConnection, TenantPermission, TenantRoleMapping |
| **Ambivalent** | AuditLog (nur tenant_id, kein workspace_id), OperationRun (hat workspace_id, aber kein Immutabilitäts-Trait), AlertDelivery (workspace_id + optional tenant_id) |
### Capability-Muster
**Klar und konsistent:**
- Capability-first Design über `Capabilities::*` und `PlatformCapabilities::*`
- Static Registry (`Capabilities::all()`, `Capabilities::isKnown()`)
- Rolle→Capability Mapping über `RoleCapabilityMap` und `WorkspaceRoleCapabilityMap`
- Gates werden dynamisch aus Capabilities registriert (AuthServiceProvider)
- UiEnforcement erzwingt dreilagige Autorisierung (Visibility → Disabled → Server Guard)
**Inkonsistent:**
- Einige Policies nutzen `Gate::allows()`, andere nutzen `CapabilityResolver::can()` direkt
- System-Panel nutzt separate `PlatformCapabilities` ohne Kompartimentierung innerhalb des System-Scopes
- AuditLog hat keinen eigenen Capability-Check — Sichtbarkeit wird durch Page-Navigation gesteuert
---
## 3. Findings Table
| ID | Severity | Kategorie | Datei / Klasse | Scope | UI-Symptom | Technische Ursache | Risiko | Empfohlene Korrektur |
|----|----------|-----------|----------------|-------|------------|---------------------|--------|---------------------|
| F-01 | **CRITICAL** | Cross-Scope Data Leak | `App\Filament\System\Widgets\ControlTowerTopOffenders` | System→All | Widget zeigt Top-10-Fehler mit Workspace+Tenant-Namen | Query ohne jegliche Scope-Filterung: `OperationRun::query()->selectRaw('workspace_id, tenant_id...')` | Jeder PlatformUser sieht Betriebsdaten aller Mandanten | Capability-basierte Kompartimentierung oder Anonymisierung im System-Panel |
| F-02 | **CRITICAL** | Cross-Scope Data Leak | `App\Filament\System\Widgets\ControlTowerKpis` | System→All | KPI-Dashboard zeigt Gesamtstatistiken über alle Workspaces | Globale `OperationRun::query()` ohne WHERE-Klausel | Informations-Leaking über Betriebsumfang und Fehlerquoten | Scope-Filter oder Capability-Granularisierung |
| F-03 | **CRITICAL** | Cross-Scope Data Leak | `App\Filament\System\Widgets\ControlTowerRecentFailures` | System→All | Zeigt letzte fehlgeschlagene Runs mit Workspace-Namen | Ungefilterter Query | Sensitive Betriebs-Metadaten sichtbar | Scope-aware oder anonymisiert |
| F-04 | **HIGH** | Tenant Enumeration | `App\Filament\System\Pages\Directory\Tenants` | System | Directory listet alle Tenants mit Health, Status, Permission Gaps | `Tenant::query()->with('workspace')` ohne Scope | Reconnaissance: Angreifer sieht alle Mandanten, deren Konfigurationsprobleme und Provider-Status | Capability-basierter Zugang oder Scope-Einschränkung |
| F-05 | **HIGH** | Workspace Enumeration | `App\Filament\System\Pages\Directory\Workspaces` | System | Directory listet alle Workspaces mit Tenant-Count und Fehlerquoten | `Workspace::query()` ohne Scope | Reconnaissance: vollständige Sicht auf Struktur und Probleme aller Organisationen | Wie F-04 |
| F-06 | **HIGH** | Missing Structural Scope | `App\Models\AuditLog` | Tenant/Workspace | AuditLog-Einträge haben kein `workspace_id` | Model hat nur `tenant_id`, kein Workspace-Feld | Bei neuen Zugangspfaden oder Queries ohne Tenant-Kontext könnten Audit-Daten cross-workspace leaken | Migration: `workspace_id` Spalte + NOT NULL + DerivesWorkspaceIdFromTenant Trait |
| F-07 | **MEDIUM** | Missing `canAccess()` | `App\Filament\Pages\Monitoring\Operations` | Admin | Seite ist über `/admin/operations` direkt erreichbar | Kein expliziter `canAccess()` Check, nur Middleware+Query-Scoping | Falls Middleware fehlschlägt oder umgangen wird, keine zweite Schutzschicht | `canAccess()` mit WorkspaceCapabilityResolver implementieren |
| F-08 | **MEDIUM** | Missing `canAccess()` | `App\Filament\Pages\Monitoring\AuditLog` | Admin | Seite über `/admin/audit-log` erreichbar, `shouldRegisterNavigation=false` | Kein `canAccess()`, nur `shouldRegisterNavigation=false` und OperateHubShell | Hidden page ohne Page-Level Auth ist per URL erreichbar | `canAccess()` implementieren |
| F-09 | **MEDIUM** | URL Enumeration | Notification Deep Links + Route `{run}` | Tenant | OperationRun-IDs in Notification-URLs sind sequential integers | `OperationRunLinks::tenantlessView($run)` nutzt Integer-ID | ID-Enumeration erlaubt Erkennung valider Run-IDs (Auth verhindert Lesezugriff) | UUID-basierte Route-Keys oder Rate-Limiting |
| F-10 | **MEDIUM** | URL Enumeration | Finding-URLs in Tenant Panel | Tenant | Finding-IDs in Links: `/findings/140`, `/findings/139` | Sequential integer keys in URLs | Ähnlich wie F-09 | UUID-basierte Anzeige |
| F-11 | **MEDIUM** | No Model Immutability | `App\Models\OperationRun` | Workspace/Tenant | N/A | Kein `DerivesWorkspaceIdFromTenant` Trait; workspace_id wird per boot() auto-filled, aber nicht immutabel erzwungen | Theoretische Reassignment-Möglichkeit (kein aktueller Exploit-Pfad) | DerivesWorkspaceIdFromTenant Trait hinzufügen |
| F-12 | **MEDIUM** | No Model Immutability | `App\Models\BaselineProfile` | Workspace | N/A | Kein Scoping-Trait; workspace_id muss bei Creation gesetzt werden | Developer-Fehler bei zukünftigen Code-Änderungen möglich | Trait oder DB-Constraint |
| F-13 | **LOW** | Break-Glass Scope | `App\Filament\System\Pages\RepairWorkspaceOwners` | System | PlatformUser kann Owner für beliebigen Workspace zuweisen | Nur `USE_BREAK_GLASS` Capability geprüft, keine Workspace-Zugehörigkeitsvalidierung | Intentional (Emergency-Feature), aber unkompartimentiert | Dokumentation und Audit-Alerting sicherstellen; optional: Require 4-eyes |
| F-14 | **LOW** | Access Log Exposure | `App\Filament\System\Pages\Security\AccessLogs` | System | Zeigt alle Platform-Logins und Break-Glass-Aktivitäten | Nur `CONSOLE_VIEW` Capability erforderlich | Offenlegen, wer System-Zugang hat und wann | Separate Capability `SECURITY_AUDIT_VIEW` |
| F-15 | **LOW** | Livewire Auth Gap | `App\Livewire\SettingsCatalogSettingsTable` | Tenant | Liest Policy-Einstellungen ohne eigene Auth | Kein eigener Authorization-Check; angenommen: nur auf autorisierten Seiten gemounted | Falls auf anderer Seite gemounted, Daten-Leak möglich | Defensive Auth prüfen oder Assert bei mount() |
---
## 4. Mismatch Matrix
| Dimension | Status | Details |
|-----------|--------|---------|
| **Navigation Visibility ↔ Policy Enforcement** | ✅ Konsistent für Tenant-Panel | Navigation items nutzen `canViewAny()` + Policy-Checks; UiEnforcement erzwingt Dreischicht-Modell |
| **Navigation Visibility ↔ Policy Enforcement** | ⚠️ Inkonsistent für Monitoring-Pages | Operations und AuditLog sind `shouldRegisterNavigation=false` aber per URL erreichbar ohne `canAccess()` |
| **Navigation Visibility ↔ Policy Enforcement** | ⚠️ Inkonsistent für System-Panel | System-Pages haben nur `DIRECTORY_VIEW`/`OPERATIONS_VIEW` Capability, aber zeigen Daten aller Mandanten |
| **Query/Data Scope ↔ Navigation Scope** | ✅ Konsistent für Tenant-scoped Resources | `getEloquentQuery()` filtert konsequent nach `Tenant::current()` |
| **Query/Data Scope ↔ Navigation Scope** | ✅ Konsistent für Workspace-scoped Resources | `getEloquentQuery()` filtert nach `WorkspaceContext::currentWorkspaceId()` mit `whereRaw('1 = 0')` als Fallback |
| **Query/Data Scope ↔ Navigation Scope** | ⚠️ System-Widgets: Kein Scope, Navigation erlaubt | ControlTower-Widgets haben weder Query-Scope noch Navigation-Gate |
| **Route Accessibility ↔ Authorization** | ✅ Tenant Panel Seiten | Middleware-Stack (`ensure-workspace-selected`, `ensure-filament-tenant-selected`, `DenyNonMemberTenantAccess`) |
| **Route Accessibility ↔ Authorization** | ✅ Tenantless Run Viewer | `mount()``$this->authorize('view', $run)` → OperationRunPolicy prüft Workspace+Tenant+Capability |
| **Route Accessibility ↔ Authorization** | ⚠️ AuditLog Page | `shouldRegisterNavigation=false`, aber kein `canAccess()` — per URL erreichbar |
| **Action Auth ↔ Bulk Action Auth** | ✅ Konsistent | UiEnforcement erzwingt all-or-nothing Semantik für Bulk Actions mit gleicher Capability wie Single Actions |
| **Widget Data ↔ User Scope** | ✅ Dashboard-Widgets | `DashboardKpis`, `NeedsAttention`, `RecentOperations` nutzen `Filament::getTenant()` korrekt |
| **Widget Data ↔ User Scope** | ✅ AlertsKpiHeader | Filtert nach workspace_id + user.tenantMemberships + optional activeTenant |
| **Widget Data ↔ User Scope** | ⚠️ ControlTower-Widgets | Keine Scope-Filterung — zeigen Platform-weite Aggregate |
| **Global Search ↔ Scope** | ✅ ScopesGlobalSearchToTenant Trait | Prüft `canAccessTenant()` und filtert per `whereBelongsTo()` |
| **Global Search ↔ Scope** | ⚠️ Trait-Nutzung verifizieren | Unklar ob alle globalsuchbaren Resources diesen Trait nutzen |
| **Relation Managers ↔ Parent Scope** | ✅ Konsistent | TenantMembershipsRelationManager, BackupItemsRelationManager etc. erben Parent-Scope des owning Resource |
| **Deep Links ↔ Auth** | ✅ Notification-URLs | OperationRunPolicy prüft Workspace+Tenant bei mount() |
| **Deep Links ↔ Auth** | ⚠️ IDs in URLs | Sequential integers ermöglichen Enumeration |
---
## 5. Target Enterprise RBAC Model
### Rollen-/Capability-Modell (Zielzustand)
```
Platform Layer
├── Platform Admin: Full system access
├── Platform Ops: Operations monitoring (scoped)
├── Platform Audit: Security audit view only
└── Platform Support: Break-glass + Directory (audited, 4-eyes for critical)
Workspace Layer
├── Owner: Full workspace control + all tenant capabilities
├── Manager: Workspace admin (no archive/delete) + most tenant capabilities
├── Operator: Execute operations + view
└── Readonly: View-only
Tenant Layer
├── Owner: Full tenant control
├── Manager: Admin (no delete/membership manage)
├── Operator: Execute sync/backup/run + view
└── Readonly: View-only
```
### Regeln für Navigation Visibility
1. **Navigation zeigt NUR Items, für die der Nutzer authorization hat** — sowohl `shouldRegisterNavigation()` als auch `canAccess()` müssen konsistent sein
2. **Hidden Pages müssen `canAccess()` implementieren**`shouldRegisterNavigation=false` bedeutet nicht "keine Autorisierung nötig"
3. **System-Panel Navigation muss capability-granular sein** — nicht eine einzige `CONSOLE_VIEW` Capability für alles
4. **Navigation darf niemals die einzige Schutzschicht sein**
### Regeln für Query Scope
1. **Jede `getEloquentQuery()` muss scope-aware sein** — mit `whereRaw('1 = 0')` als Fallback bei fehlendem Kontext
2. **Tenant-scoped Modelle nutzen `DerivesWorkspaceIdFromTenant` oder `$tenantOwnershipRelationshipName`**
3. **Workspace-scoped Modelle filtern nach `WorkspaceContext::currentWorkspaceId()`**
4. **System-Queries (ControlTower) müssen entweder scope-kompartimentiert oder anonymisiert sein**
5. **AuditLog muss `workspace_id` als strukturelles Scope-Feld haben**
### Regeln für Direct URL Access
1. **Jede Page muss `canAccess()` implementieren** — keine Ausnahme für "hidden" Pages
2. **Route Model Binding muss Scope-Ownership erzwingen** — OperationRunPolicy-Muster als Standard
3. **UUID-basierte Route-Keys für sensitive Ressourcen** — Findings, OperationRuns
4. **Rate-Limiting auf Detail-Endpoints** — gegen Enumeration
### Regeln für Actions / Bulk Actions
1. **UiEnforcement ist der Standard für alle Actions** — kein Bypass
2. **Destructive Actions: `->requiresConfirmation()` + `->action(...)` + Capability-Check**
3. **Bulk Actions: all-or-nothing Semantik (bereits implementiert)**
4. **Keine Action darf schwächer autorisiert sein als die entsprechende Single-Record-Action**
### Regeln für Widgets, Search, Exports und Deep Links
1. **Widgets dürfen nur scope-konforme Daten anzeigen** — kein Count über unerlaubte Scopes
2. **Global Search muss `ScopesGlobalSearchToTenant` nutzen** — für alle tenant-scoped Resources
3. **Deep Links in Notifications müssen Policy-geschützt sein** — bereits implementiert, UUID-Keys evaluieren
4. **Export-Funktionen müssen dieselbe Scope-Filterung wie die UI anwenden**
---
## 6. Hardening Recommendations
### Quick Wins (P0-P1, sofort umsetzbar)
| # | Maßnahme | Nutzen | Risiko | Aufwand | Priorität |
|---|----------|--------|--------|---------|-----------|
| QW-1 | `canAccess()` auf `Operations.php` implementieren — WorkspaceCapabilityResolver + Membership Check | Schließt Page-Level Auth Lücke | Minimal: bestehende Pattern kopieren | 1h | P1 |
| QW-2 | `canAccess()` auf `AuditLog.php` implementieren | Wie QW-1 | Minimal | 1h | P1 |
| QW-3 | System-Panel Capability-Granularisierung: `OPERATIONS_VIEW` in `OPS_VIEW_RUNS`, `OPS_VIEW_FAILURES`, `OPS_VIEW_STUCK` aufteilen | Feinere Zugriffskontrolle | Migration + PlatformUser Capability-Update | 4h | P1 |
| QW-4 | ControlTower-Widgets: Option für anonymisierte/aggregierte Ansicht | Verhindert Tenant-Namen-Leak | Widget-Refactor | 4h | P1 |
| QW-5 | `SettingsCatalogSettingsTable` Livewire: Defensive Auth in `mount()` | Verhindert Context-Leak bei Wiederverwendung | Minimal | 30min | P2 |
### Mittlere Hardening-Maßnahmen (P2, geplant)
| # | Maßnahme | Nutzen | Risiko | Aufwand | Priorität |
|---|----------|--------|--------|---------|-----------|
| MH-1 | AuditLog Migration: `workspace_id` Spalte + NOT NULL + Index | Strukturelles Workspace-Scoping | Backfill-Migration nötig | 8h | P2 |
| MH-2 | `DerivesWorkspaceIdFromTenant` auf OperationRun-Model | Immutabilitäts-Garantie | Sehr gering | 2h | P2 |
| MH-3 | `DerivesWorkspaceIdFromTenant` auf BaselineProfile-Model | Wie MH-2 | Gering | 2h | P2 |
| MH-4 | UUID-basierte Route-Keys für Finding, OperationRun | Verhindert ID-Enumeration | Model+Migration+Route-Refactor | 12h | P2 |
| MH-5 | Rate-Limiting auf `/admin/operations/{run}` und ähnliche Detail-Endpoints | Anti-Enumeration | Middleware-Config | 2h | P2 |
| MH-6 | System-Panel Directory: Capability `DIRECTORY_MANAGE` vs `DIRECTORY_VIEW` differenzieren | Least-Privilege im System-Panel | Capability-Registry Update | 4h | P2 |
| MH-7 | Break-Glass: 4-eyes-Prinzip oder temporal scoping verschärfen | Privileged-Access-Governance | UX-Design nötig | 8h | P2 |
### Strukturelle Refactors (P3, Spec-gesteuert)
| # | Maßnahme | Nutzen | Risiko | Aufwand | Priorität | Spec? |
|---|----------|--------|--------|---------|-----------|-------|
| SR-1 | Global Scope auf OperationRun für automatisches Workspace-Scoping | Präventiv gegen zukünftige Query-Fehler | Model-Behavior-Änderung; Tests nötig | 16h | P3 | Ja |
| SR-2 | System-Panel Kompartimentierung: Scope-basierte Platform Views | Enterprise isolation für managed-service Betrieb | Architektur-Refactor | 40h | P3 | Ja |
| SR-3 | Einheitliche `canAccess()` Base-Mixin für alle Pages | DRY + Konsistenz | Base-Class Refactor | 16h | P3 | Ja |
| SR-4 | Monitoring-Pages: Unified OperateHub Authorization Layer | Konsolidierte Auth statt Page-for-Page | Architektur | 24h | P3 | Ja |
---
## 7. Project Rules
### Verbindliche Enterprise RBAC Rules
1. **Navigation ist nie die Sicherheitsgrenze.** Jede Filament-Page muss eine `canAccess()` Methode implementieren, die unabhängig von Navigation Visibility autorisiert. `shouldRegisterNavigation=false` ersetzt keinen Authorization-Check.
2. **Jede sichtbare Aktion braucht deckungsgleiche Server-Autorisierung.** UiEnforcement (Tenant) oder WorkspaceUiEnforcement (Workspace) ist der Standard-Wrapper für alle Actions. Kein Action-Handler darf ohne Server-Side Guard ausgeführt werden.
3. **Hidden Pages dürfen nicht unautorisiert per URL erreichbar sein.** Jede Page mit `shouldRegisterNavigation=false` oder `isDiscovered=false` MUSS explizit `canAccess()` implementieren.
4. **Workspace- und Tenant-Scope müssen in Query, Route Binding und Policy konsistent erzwungen werden.** Scope-Enforcement findet auf mindestens zwei Ebenen statt: Query-Level (`getEloquentQuery()`) UND Policy/Gate-Level.
5. **Widgets, Counts und Search dürfen keine scope-fremden Metadaten leaken.** Jedes Dashboard-Widget muss denselben Scope-Filter anwenden wie die zugehörige Tabelle/Resource. System-Widgets müssen scope-kompartimentiert oder anonymisiert sein.
6. **Bulk Actions dürfen nie schwächer autorisiert sein als Single Actions.** UiEnforcement erzwingt all-or-nothing Semantik: Wenn ein Record in der Selektion die Capability verletzt, wird die gesamte Bulk-Action blockiert.
7. **Deep Links und Notifications müssen kanonisch und permission-sicher sein.** Jede URL, die per Notification, E-Mail oder API-Response geteilt wird, muss bei Zugriff denselben Auth-Check durchlaufen wie der UI-Pfad.
8. **Route Model Binding muss Scope-Ownership erzwingen.** Policies müssen bei `view()` immer Workspace-Membership UND Tenant-Membership (falls tenant-scoped) prüfen. Non-Members erhalten 404 (deny-as-not-found), nicht 403.
9. **Capability-first vor rollen-/UI-getriebener Logik.** Autorisierung leitet sich aus Capabilities ab, nicht aus Rollen-Strings oder UI-Zustand. `RoleCapabilityMap` ist die Single Source of Truth.
10. **Kein Zugriff über "zufällig funktionierende" indirekte Pfade.** Jeder neue Pfad (Relation Manager, Tab, Widget, Global Search, Export) muss explizit scope-geprüft werden. Filament Auto-Discovery allein ist keine Autorisierung.
11. **Modelle mit Tenant- oder Workspace-Zugehörigkeit MÜSSEN Immutabilitäts-Traits nutzen.** `DerivesWorkspaceIdFromTenant` oder vergleichbare Traits stellen sicher, dass Scope-Zuweisungen nach Creation nicht verändert werden können.
12. **System-Panel Capabilities müssen granular sein.** Eine einzige `CONSOLE_VIEW` Capability für cross-workspace Sichtbarkeit reicht für Enterprise-Betrieb nicht aus. Differenzierung in Operations, Directory, Security, Break-Glass.
13. **Destructive Actions erfordern immer `->requiresConfirmation()`.** Keine Ausnahme, auch nicht bei "schnellen" Aktionen wie Archive oder Toggle.
14. **AuditLog muss workspace-scoped sein.** Jeder Audit-Eintrag muss `workspace_id` tragen, um cross-workspace Reporting und Compliance zu ermöglichen.
---
## 8. Spec Proposal Backlog
### Spec A: System Panel Kompartimentierung
| Feld | Wert |
|------|------|
| **Titel** | System Panel Capability Granularization & Data Compartmentalization |
| **Problem** | Alle PlatformUser mit `CONSOLE_VIEW` sehen Betriebsdaten aller Workspaces/Tenants. ControlTower-Widgets, Directory-Pages und Access-Logs bieten keine Scope-Isolation. |
| **Ziel** | PlatformCapabilities aufteilen in `OPS_VIEW_RUNS`, `OPS_VIEW_FAILURES`, `DIRECTORY_VIEW_TENANTS`, `DIRECTORY_VIEW_WORKSPACES`, `SECURITY_AUDIT_VIEW`. ControlTower-Widgets anonymisieren oder scope-kompartimentieren. |
| **Scope** | System Panel Widgets, Directory Pages, Access Logs, PlatformCapabilities Registry |
| **Non-goals** | Nicht: Umbau der web-Guard-Architektur; nicht: Multi-Panel Redesign |
| **Priorität** | P1 — Enterprise-Compliance-Risiko |
| **Abhängigkeiten** | Keine |
### Spec B: Page-Level Authorization Enforcement
| Feld | Wert |
|------|------|
| **Titel** | Enforce canAccess() on all Filament Pages |
| **Problem** | Mehrere Pages (Operations, AuditLog, potentiell weitere) haben keinen expliziten `canAccess()` Check und verlassen sich nur auf Middleware |
| **Ziel** | Alle Pages im Admin/Tenant/System Panel implementieren `canAccess()` mit CapabilityResolver/WorkspaceCapabilityResolver |
| **Scope** | Alle Filament Pages; PHPStan oder Pest-Regel zur Enforcement-Prüfung |
| **Non-goals** | Nicht: Middleware-Umbau; nicht: UiEnforcement-Refactor |
| **Priorität** | P1 — Defense-in-Depth |
| **Abhängigkeiten** | Keine |
### Spec C: AuditLog Workspace Scoping
| Feld | Wert |
|------|------|
| **Titel** | Add workspace_id to AuditLog for Structural Scope Isolation |
| **Problem** | AuditLog hat nur `tenant_id`, kein `workspace_id`. Query-Level-Scoping ist fragil und kann bei neuen Zugangspfaden umgangen werden. |
| **Ziel** | Migration: `workspace_id` Spalte (nullable → later NOT NULL); Backfill-Job; DerivesWorkspaceIdFromTenant-Trait; Index |
| **Scope** | AuditLog Model, Migration, Backfill, Audit-Service Updates |
| **Non-goals** | Nicht: AuditLog Schema-Redesign; nicht: Retention-Policy |
| **Priorität** | P2 |
| **Abhängigkeiten** | Keine |
### Spec D: Model Immutability Enforcement
| Feld | Wert |
|------|------|
| **Titel** | Apply DerivesWorkspaceId Trait to OperationRun, BaselineProfile, AlertRule |
| **Problem** | OperationRun, BaselineProfile, AlertRule haben keine Model-Level Immutabilitäts-Garantie für workspace_id. Scope-Zuweisungen könnten theoretisch nach Creation verändert werden. |
| **Ziel** | DerivesWorkspaceIdFromTenant oder DerivesWorkspaceIdFromTenantWhenPresent Trait auf alle scope-relevanten Modelle anwenden; WorkspaceIsolationViolation als Guard |
| **Scope** | OperationRun, BaselineProfile, AlertRule, AlertDestination Models |
| **Non-goals** | Nicht: Global Scope Enforcement (separate Spec) |
| **Priorität** | P2 |
| **Abhängigkeiten** | Keine |
### Spec E: UUID Route Keys für Sensitive Resources
| Feld | Wert |
|------|------|
| **Titel** | UUID-based Route Keys for Findings, OperationRuns, and Sensitive Records |
| **Problem** | Sequential integer IDs in URLs ermöglichen Enumeration. Notification-Deep-Links und Tabellen-Links enthalten vorhersagbare IDs. |
| **Ziel** | getRouteKeyName() auf `external_id` (UUID) umstellen für Finding, OperationRun; Migration für UUID-Spalte; URL-Refactor |
| **Scope** | Finding, OperationRun, ReviewPack Models; Routes; Notification Links |
| **Non-goals** | Nicht: Alle Models umstellen; nicht: bestehende DB-PKs ändern |
| **Priorität** | P2 |
| **Abhängigkeiten** | Keine |
### Spec F: OperateHub Unified Authorization Layer
| Feld | Wert |
|------|------|
| **Titel** | Unified Authorization Layer for Monitoring/OperateHub Pages |
| **Problem** | Monitoring-Pages (Operations, Alerts, AuditLog) haben unterschiedliche Auth-Patterns. Operations nutzt kein `canAccess()`, Alerts nutzt WorkspaceCapabilityResolver, AuditLog nutzt keines. |
| **Ziel** | Shared Trait oder Base-Page-Klasse für OperateHub-Pages mit einheitlicher `canAccess()` + Capability-Mapping |
| **Scope** | OperateHub Pages, Monitoring Cluster, Shared Trait |
| **Non-goals** | Nicht: Redesign der OperateHubShell Navigation-Logik |
| **Priorität** | P2 |
| **Abhängigkeiten** | Spec B (Page-Level Auth Enforcement) |
### Spec G: Global Search Scope Verification & Trait Coverage
| Feld | Wert |
|------|------|
| **Titel** | Verify and Enforce ScopesGlobalSearchToTenant Usage on All Searchable Resources |
| **Problem** | `ScopesGlobalSearchToTenant` Trait existiert und ist gut implementiert, aber es ist unklar, ob alle global-suchbaren Resources ihn nutzen. |
| **Ziel** | Audit: Welche Resources sind global-suchbar? Nutzen alle den Trait? Pest-Test für Enforcement. |
| **Scope** | Alle Filament Resources mit $recordTitleAttribute oder globallySearchable |
| **Non-goals** | Nicht: Redesign der Global-Search-Architektur |
| **Priorität** | P2 |
| **Abhängigkeiten** | Keine |
---
## Appendix: Positive Architecture Highlights
### Was gut funktioniert
1. **Capability-first Design** mit `Capabilities::all()` als Single Source of Truth und dynamischer Gate-Registrierung
2. **UiEnforcement** als dreilagiger RBAC-Wrapper (Visibility → Disabled → Server Guard) — Enterprise-Grade
3. **Deny-as-not-found Semantik** — Non-Members bekommen 404, nicht 403 (verhindert Existence-Leaking)
4. **DerivesWorkspaceIdFromTenant Trait** — Model-Level Immutabilität mit `WorkspaceIsolationViolation` Exception
5. **Last-Owner Guard** — Verhindert Removal/Demotion des letzten Workspace/Tenant Owners
6. **Workspace 7-Step Selection Algorithm** — Robust mit Stale-Session-Handling und Audit-Events
7. **Tenant Middleware Stack** — 4 Middleware-Layer (correct guard → workspace selected → tenant selected → deny non-member)
8. **Break-Glass Auditing** — Vollständig auditiert mit TTL, explicit start/exit, IP-Logging
9. **OperateHubShell** — Konsistente Cross-Tenant-Sicht mit Membership-Validation und Entitlement-Prüfung
10. **WriteGateInterface** — Hardening-Layer gegen unsichere RBAC-Zustände bei Intune-Writes
### Zusammenfassung
Die Architektur zeigt ein **reifes, mehrschichtiges Autorisierungsmodell**, das für die meisten Enterprise-Szenarien robust ist. Die identifizierten Lücken betreffen primär:
1. **System-Panel**: Unzureichende Kompartimentierung (alle PlatformUser sehen alles)
2. **Monitoring-Pages**: Fehlende explizite Page-Level Auth (Defense-in-Depth Lücke)
3. **AuditLog**: Fehlendes strukturelles Workspace-Scoping
4. **ID-Enumeration**: Sequenzielle IDs in URLs (kein Datenleak, aber Reconnaissance-Vektor)
Keines dieser Findings ermöglicht aktuell einen direkten Daten-Leak oder Privilege-Escalation über die bestehenden Middleware- und Policy-Checks hinaus. Die Maßnahmen dienen der **Enterprise-Härtung und Defense-in-Depth**, nicht der Schließung aktiver Sicherheitslücken.