Aufsicht und Eingriff getrennt: neuer Haken "Audit-Betrieb"

Die DSGVO-Rolle trug audit:* komplett, also auch audit:admin. Ein
DSGVO-Beauftragter konnte damit seal-backlog, rehash und cleanup - seine
eigene Beweisgrundlage ersetzen. Wer das Protokoll beaufsichtigt, darf es
nicht umschreiben koennen. Dieselbe Klasse wie R184-02: falsche Domaene,
zu breit gebuendelt.

Der naheliegende Fix waere falsch gewesen. audit:admin einfach aus der
DSGVO-Rolle zu streichen haette es heimatlos gemacht: Die Admin-Rolle ist
ausdruecklich ohne audit/gdpr gebaut, einzige verbleibende Quelle waere
der Entwicklerzugriff - der alles gibt. Prod versiegeln haette dann
Vollzugriff vorausgesetzt.

Deshalb eine eigene versteckte Rolle "Audit-Betrieb" (audit:read +
audit:admin), zugewiesen ueber eine Checkbox wie DSGVO/Entwickler. DSGVO
behaelt audit:read + audit:export + gdpr:*. Fuer kein bestehendes Konto
weitet sich etwas aus; es wird enger, und wer eingreifen koennen soll,
bekommt es ausdruecklich.

Keine zusaetzliche Rechte-Huerde davor, weil das am Henne-Ei-Problem
scheitert: Nach der Aufteilung haelt zunaechst niemand audit:admin,
koennte ihn also auch niemand vergeben. Stattdessen wird die Vergabe
laut - CRITICAL im Protokoll und PERMISSION_CHANGED/CRITICAL im
Alarmkanal, samt Kennzeichen, ob sich jemand den Haken selbst gesetzt hat.

Nebenbei geschlossen: setUserGdprAccess() legte die DSGVO-Rolle im
Notfallpfad mit audit:* komplett an - eine zweite Liste, die dasselbe
bedeuten sollte und die Buendelung stillschweigend zurueckgebracht haette.

ACHTUNG beim Deploy: Bestehende DSGVO-Konten verlieren audit:admin. Wer
Prod versiegeln will, muss sich vorher "Audit-Betrieb" ankreuzen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 17:16:27 +02:00
co-authored by Claude Opus 5
parent 909e523634
commit fc6f39eba0
8 changed files with 232 additions and 21 deletions
+3 -2
View File
@@ -1548,11 +1548,12 @@ export const userApi = {
const res = await api.get<ApiResponse<User>>(`/users/${id}`);
return res.data;
},
create: async (data: { email: string; password: string; firstName: string; lastName: string; roleIds: number[]; customerId?: number; hasDeveloperAccess?: boolean; hasGdprAccess?: boolean; isServiceAccount?: boolean; confirm?: string; whatsappNumber?: string; telegramUsername?: string; signalNumber?: string }) => {
create: async (data: { email: string; password: string; firstName: string; lastName: string; roleIds: number[]; customerId?: number; hasDeveloperAccess?: boolean; hasGdprAccess?: boolean;
hasAuditOpsAccess?: boolean; isServiceAccount?: boolean; confirm?: string; whatsappNumber?: string; telegramUsername?: string; signalNumber?: string }) => {
const res = await api.post<ApiResponse<User>>('/users', data);
return res.data;
},
update: async (id: number, data: Partial<User> & { password?: string; roleIds?: number[]; isServiceAccount?: boolean; confirm?: string }) => {
update: async (id: number, data: Partial<User> & { password?: string; roleIds?: number[]; isServiceAccount?: boolean; hasGdprAccess?: boolean; hasDeveloperAccess?: boolean; hasAuditOpsAccess?: boolean; confirm?: string }) => {
const res = await api.put<ApiResponse<User>>(`/users/${id}`, data);
return res.data;
},