## Summary - add Intune RBAC role definitions and role assignments as foundation-backed inventory, backup, and versioned snapshot types - add RBAC-specific normalization, coverage, permission-warning handling, and preview-only restore safety behavior across existing Filament and service surfaces - add spec 127 artifacts, contracts, audits, and focused regression coverage for inventory, backup, versioning, verification, and authorization behavior ## Testing - `vendor/bin/sail bin pint --dirty --format agent` - `vendor/bin/sail artisan test --compact tests/Feature/Inventory/InventorySyncServiceTest.php tests/Feature/Filament/InventoryCoverageTableTest.php tests/Feature/FoundationBackupTest.php tests/Feature/Filament/RestoreExecutionTest.php tests/Feature/RestoreUnknownPolicyTypeSafetyTest.php tests/Unit/GraphContractRegistryTest.php tests/Unit/FoundationSnapshotServiceTest.php tests/Feature/Verification/IntuneRbacPermissionCoverageTest.php tests/Unit/IntuneRoleDefinitionNormalizerTest.php tests/Unit/IntuneRoleAssignmentNormalizerTest.php` ## Notes - tasks in `specs/127-rbac-inventory-backup/tasks.md` are complete except `T041`, which is the documented manual QA validation step Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #155
29 KiB
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_idSpalte 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::*undPlatformCapabilities::* - Static Registry (
Capabilities::all(),Capabilities::isKnown()) - Rolle→Capability Mapping über
RoleCapabilityMapundWorkspaceRoleCapabilityMap - Gates werden dynamisch aus Capabilities registriert (AuthServiceProvider)
- UiEnforcement erzwingt dreilagige Autorisierung (Visibility → Disabled → Server Guard)
Inkonsistent:
- Einige Policies nutzen
Gate::allows(), andere nutzenCapabilityResolver::can()direkt - System-Panel nutzt separate
PlatformCapabilitiesohne 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
- Navigation zeigt NUR Items, für die der Nutzer authorization hat — sowohl
shouldRegisterNavigation()als auchcanAccess()müssen konsistent sein - Hidden Pages müssen
canAccess()implementieren —shouldRegisterNavigation=falsebedeutet nicht "keine Autorisierung nötig" - System-Panel Navigation muss capability-granular sein — nicht eine einzige
CONSOLE_VIEWCapability für alles - Navigation darf niemals die einzige Schutzschicht sein
Regeln für Query Scope
- Jede
getEloquentQuery()muss scope-aware sein — mitwhereRaw('1 = 0')als Fallback bei fehlendem Kontext - Tenant-scoped Modelle nutzen
DerivesWorkspaceIdFromTenantoder$tenantOwnershipRelationshipName - Workspace-scoped Modelle filtern nach
WorkspaceContext::currentWorkspaceId() - System-Queries (ControlTower) müssen entweder scope-kompartimentiert oder anonymisiert sein
- AuditLog muss
workspace_idals strukturelles Scope-Feld haben
Regeln für Direct URL Access
- Jede Page muss
canAccess()implementieren — keine Ausnahme für "hidden" Pages - Route Model Binding muss Scope-Ownership erzwingen — OperationRunPolicy-Muster als Standard
- UUID-basierte Route-Keys für sensitive Ressourcen — Findings, OperationRuns
- Rate-Limiting auf Detail-Endpoints — gegen Enumeration
Regeln für Actions / Bulk Actions
- UiEnforcement ist der Standard für alle Actions — kein Bypass
- Destructive Actions:
->requiresConfirmation()+->action(...)+ Capability-Check - Bulk Actions: all-or-nothing Semantik (bereits implementiert)
- Keine Action darf schwächer autorisiert sein als die entsprechende Single-Record-Action
Regeln für Widgets, Search, Exports und Deep Links
- Widgets dürfen nur scope-konforme Daten anzeigen — kein Count über unerlaubte Scopes
- Global Search muss
ScopesGlobalSearchToTenantnutzen — für alle tenant-scoped Resources - Deep Links in Notifications müssen Policy-geschützt sein — bereits implementiert, UUID-Keys evaluieren
- 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
-
Navigation ist nie die Sicherheitsgrenze. Jede Filament-Page muss eine
canAccess()Methode implementieren, die unabhängig von Navigation Visibility autorisiert.shouldRegisterNavigation=falseersetzt keinen Authorization-Check. -
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.
-
Hidden Pages dürfen nicht unautorisiert per URL erreichbar sein. Jede Page mit
shouldRegisterNavigation=falseoderisDiscovered=falseMUSS explizitcanAccess()implementieren. -
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. -
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.
-
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.
-
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.
-
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. -
Capability-first vor rollen-/UI-getriebener Logik. Autorisierung leitet sich aus Capabilities ab, nicht aus Rollen-Strings oder UI-Zustand.
RoleCapabilityMapist die Single Source of Truth. -
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.
-
Modelle mit Tenant- oder Workspace-Zugehörigkeit MÜSSEN Immutabilitäts-Traits nutzen.
DerivesWorkspaceIdFromTenantoder vergleichbare Traits stellen sicher, dass Scope-Zuweisungen nach Creation nicht verändert werden können. -
System-Panel Capabilities müssen granular sein. Eine einzige
CONSOLE_VIEWCapability für cross-workspace Sichtbarkeit reicht für Enterprise-Betrieb nicht aus. Differenzierung in Operations, Directory, Security, Break-Glass. -
Destructive Actions erfordern immer
->requiresConfirmation(). Keine Ausnahme, auch nicht bei "schnellen" Aktionen wie Archive oder Toggle. -
AuditLog muss workspace-scoped sein. Jeder Audit-Eintrag muss
workspace_idtragen, 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
- Capability-first Design mit
Capabilities::all()als Single Source of Truth und dynamischer Gate-Registrierung - UiEnforcement als dreilagiger RBAC-Wrapper (Visibility → Disabled → Server Guard) — Enterprise-Grade
- Deny-as-not-found Semantik — Non-Members bekommen 404, nicht 403 (verhindert Existence-Leaking)
- DerivesWorkspaceIdFromTenant Trait — Model-Level Immutabilität mit
WorkspaceIsolationViolationException - Last-Owner Guard — Verhindert Removal/Demotion des letzten Workspace/Tenant Owners
- Workspace 7-Step Selection Algorithm — Robust mit Stale-Session-Handling und Audit-Events
- Tenant Middleware Stack — 4 Middleware-Layer (correct guard → workspace selected → tenant selected → deny non-member)
- Break-Glass Auditing — Vollständig auditiert mit TTL, explicit start/exit, IP-Logging
- OperateHubShell — Konsistente Cross-Tenant-Sicht mit Membership-Validation und Entitlement-Prüfung
- 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:
- System-Panel: Unzureichende Kompartimentierung (alle PlatformUser sehen alles)
- Monitoring-Pages: Fehlende explizite Page-Level Auth (Defense-in-Depth Lücke)
- AuditLog: Fehlendes strukturelles Workspace-Scoping
- 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.