Skip to content

Audit-Log

Jede systemrelevante Aktion im CMS landet in einem manipulationssicheren Audit-Log: Backend-Logins und 2FA-Events, Content-Aenderungen (Pagebuilder, Menue-Editor, Media), Konfigurationsaenderungen (API-Keys, AI-Provider, Shopware 6, Elasticsearch), Daten-Exporte, Deployments, Plugin-Installation/Deinstallation. Jeder Eintrag traegt den ausloesenden User, die echte Client-IP, den Browser, einen Vorher/Nachher-Snapshot und einen SHA256-HMAC-Hash, der ihn kryptographisch an den Vorgaenger-Eintrag bindet. Ein taeglicher Verifier prueft, dass die Kette nicht veraendert wurde.

Oeffne Komponenten → Log.

Audit-Log-Tabelle mit Filterleiste, Chain-Status-Badge und Severity-Spalte

Was geloggt wird

Audit-Eintraege entstehen ueber zwei Pfade:

  • Webhook-Hook — jedes interne Webhook-Event (auth.login, media.deleted, item.{table}.created/updated/deleted, deployment.completed etc.) wird automatisch mitgeloggt.
  • Direkter Hook — explizite AuditLogger::record()-Aufrufe fuer Events ohne Webhook (Failed-Logins, 2FA-Setup, Password-Resets, API-Key-Mutationen, CSV-Exporte, sensitive Reads).

Insgesamt etwa 75 unterschiedliche Event-Typen, gruppiert nach Domain-Praefix (auth.*, config.*, data.*, pagebuilder.*, menu.*, media.*, system.*, security.*, audit.*, emailmarketing.*, formfunnel.*, search.*, plus item.{table}.*-Wildcard fuer jede Plugin-Tabelle).

Schweregrade

Jeder Eintrag hat einen von drei Schweregraden. Die Header-Filter und die Zeilen-Badges nutzen das gleiche Farbschema.

LevelWann es feuertBeispiele
Info (cyan)Normale, erwartete Aktivitaet. Audit-Wert, aber kein Alarm.Erfolgreicher Login, Seite veroeffentlicht, Item erstellt/aktualisiert, sensitive Reads (Rechnung/Zertifikat/Bestelldetail), Audit-Log-Zugriff, manueller Cron-Trigger.
Warning (orange)Sicherheitsrelevant oder destruktiv. Bedarf Aufmerksamkeit.Failed Login, 2FA-Verify fehlgeschlagen, Account gesperrt, Session-IP-Wechsel, Rate-Limit ueberschritten, alle *.deleted-Events, CSV-Export, AI-Massengenerierung, Cache geleert.
Critical (rot)System-Level-Aenderungen. Pruefer sollte hier immer hinschauen.Permission geaendert, Passwort zurueckgesetzt, 2FA aktiviert, API-Key erstellt/aktualisiert/geloescht, AI-/Email-/Shopware-/Elasticsearch-Verbindung mutiert, Plugin installiert/deinstalliert, Migrationen ausgefuehrt, Deployment-Ziel angelegt/geaendert.

Auditor-Workflow

Erst auf Critical filtern — dort liegen alle privilegierten Operationen in einer Ansicht. Dann Warning fuer Security-Events und Loeschungen. Info ist fuer forensische Korrelation, wenn du den vollstaendigen Aktivitaetspfad eines Users oder einer Resource brauchst.

Spalten

SpalteBedeutung
ZeitstempelWann das Ereignis passiert ist (Server-Zeitzone).
EventHierarchischer Event-Bezeichner (domain.action.state) mit einem Icon fuer die Domain-Gruppe.
UserBackend-Benutzername, oder bei anonymen Events (Failed Login, oeffentliches Form-Submit).
ResourceDie betroffene Entitaet, z.B. user #42, page #17, apikey #5.
IPEchte Client-IP — Cloudflare-aware. Hinter einem Reverse-Proxy steht die echte Client-IP, nicht die Edge-IP.
SeverityInfo / Warning / Critical-Badge.
AktionenOeffnet das Details-Modal mit dem vollstaendigen Eintrag.

1. Log filtern

Die Filterleiste im Header schraenkt die Tabelle ein. Alle Filter kombinieren sich (UND-Verknuepfung).

FilterVerhalten
Event typeMulti-Group-Dropdown nach Authentication / Configuration / Data / Content. auth. matcht jedes Event mit auth.-Praefix; auth.login nur exakt das Event.
Resource typeFreitext-Praefix-Match auf resource_type — z.B. user, page, media, apikey.
User IDNumerisch — die ID eines Backend-Users. ID findest du unter Komponenten → Benutzer.
SeverityAlle / Info / Warning / Critical.
From / ToDatumszeit-Range. Hilfreich bei Incident-Untersuchung um einen bestimmten Zeitslot.
SucheFreitext gegen Event-Typ, Resource-ID, IP, Statement und Username.

Klick auf Reset loescht alle Filter.

2. Eintrag oeffnen

Klick auf Details in einer Zeile. Das Modal oeffnet mit allen Feldern, dem Vorher/Nachher-Diff und dem Chain-Kontext.

Audit-Log-Detail-Modal mit Vorher/Nachher-JSON-Diff und Hash-Chain-Kontext

Die Detail-Ansicht zeigt:

  • Zeitstempel, User, IP, User-Agent, Resource, Severity — die Metadaten des Eintrags.
  • Vorher / Nachher — JSON-Snapshots nebeneinander. Sensitive Felder (password, secret, key, token, salt) werden automatisch herausgefiltert. JSONs groesser 64 KB werden mit einem [truncated]-Marker abgeschnitten — der Truncation-Marker selbst gehoert zum Hash.
  • Hash-Chain — der hash des Eintrags (HMAC-SHA256 ueber Event-Typ, User, Resource, IP, Vorher/Nachher, Zeitstempel) plus der prev_hash, an den er gebunden ist.
  • Chain-Kontext — die zwei Vorgaenger und zwei Nachfolger. So siehst du den Eintrag in seiner zeitlichen Nachbarschaft.

Self-Log

Das Oeffnen des Detail-Modals schreibt selbst einen audit.read-Eintrag — der Audit-Log protokolliert, wer welche Eintraege angesehen hat. Das ist beabsichtigt und Teil des SOC2-Access-Trackings.

3. Chain verifizieren

Der Header traegt ein Chain-Status-Badge:

BadgeBedeutung
🟢 Chain OKDer Verifier lief, jeder Eintrags-Hash stimmt mit dem neu berechneten HMAC ueberein.
🟡 Never verifiedNoch kein Verifier-Run — Initialzustand bei frischer Installation.
🔴 Chain broken (pulsierend)Ein Verify-Run hat einen Mismatch gefunden. Hover ueber das Badge zeigt die ID und den Grund.

Klick auf Verify chain triggert einen On-Demand-Verifier-Lauf. Der Lauf ist asynchron: die Antwort kommt sofort zurueck, das Badge aktualisiert sich innerhalb von ~30 Sekunden ueber Polling.

Chain-Status-Badge und Verify-Button im Audit-Log-Header

Ein Scheduled Task (verify_audit_chain.php) laeuft automatisch alle 24 Stunden.

Chain broken — was tun?

Ein rotes Badge bedeutet, dass der Audit-Log ausserhalb der Anwendung manipuliert wurde. Im error_log steht eine Zeile wie [AuditChainVerifier] CHAIN BROKEN at id=X (last_verified=Y): hash mismatch on id X: expected …, got …. Untersuche, bevor du etwas anfasst: ein Angreifer mit DB-Zugriff ist der Worst Case; der zweitschlimmste Fall ist ein versehentliches ALTER TABLE / Daten-Import, das Zeilen mutiert hat.

Aufbewahrung

StufeDauerTabelle
HotErste 365 Tagelog (volle Indizes — schnelle Filter + Sortierung)
Cold-ArchivJahre 2–7log_archive (schlankere Indizes — langsamere Lookups, weniger Speicher)
Hard-CleanupNach 7 JahrenEndgueltig geloescht

Drei Scheduled Tasks halten den Lifecycle:

  • verify_audit_chain.php — taeglich, prueft die Chain.
  • archive_audit_log.php — taeglich, verschiebt Eintraege aelter als 365 Tage in log_archive (1000 Zeilen pro Batch).
  • cleanup_audit_archive.php — monatlich, loescht Archiv-Zeilen aelter als 7 Jahre.

DSGVO

IP und User-Agent sind nach DSGVO personenbezogene Daten (EuGH C-582/14, Breyer). Sie in einem Audit-Trail zu loggen ist nach Art. 6 (1) f DSGVO zulaessig (berechtigtes Interesse an IT-Sicherheit, Betrugspraevention und SOC2/ISO27001-Compliance — siehe Erwaegungsgrund 49). Voraussetzungen, damit die Rechtsgrundlage greift:

  • Die Datenschutzerklaerung erwaehnt den Audit-Log: IP, User-Agent, Aktion, Aufbewahrung.
  • Sensitive Payload-Felder (Passwoerter, Tokens, Secrets) werden automatisch gefiltert.
  • Eine definierte Aufbewahrungs-Obergrenze existiert (der 7-Jahres-Hard-Cleanup).
  • Die Chain liefert Manipulationsschutz — Voraussetzung fuer den forensischen Wert des Logs.

DSGVO-Loeschung

Einzelne Audit-Log-Zeilen auf eine Loeschungsanfrage hin zu loeschen wuerde die Hash-Chain brechen — der Verifier wuerde die Chain ab dieser Stelle als broken markieren. Empfohlene Vorgehensweise: Chain unangetastet lassen, nach dem Cleanup eine neue Chain (audit.chain_reset-Event) starten und die Begruendung im Datenschutzkonzept dokumentieren. Die Chain selbst kann einzelne Eintraege nicht rueckwirkend anonymisieren.

Export

Nutze Komponenten → CSV-Export, um die aktuelle Filteransicht als CSV herunterzuladen. Tabelle log waehlen und die gleichen Filter anwenden. Der Export selbst wird als data.export.csv (Warning) geloggt — der Audit-Trail erfasst, wer welche Daten heruntergeladen hat, inklusive Zeilenzahl und Zeitstempel.

Haeufige Fehler

Das Badge bleibt "Never verified" nach Klick auf Verify. Der Verifier laeuft asynchron. Der erste Lauf bei einem grossen Log dauert manchmal laenger als das 30-Sekunden-Polling-Fenster. Seite neu laden; wenn weiterhin "Never verified", das PHP-Error-Log auf [AuditChainVerifier]-Zeilen pruefen.

Ein User taucht in der Tabelle auf, ist aber unter Benutzer nicht mehr zu finden. Der User wurde wahrscheinlich geloescht. Audit-Eintraege behalten die originale User-ID — das ist der Sinn eines Audit-Trails. Match die ID gegen item.users.deleted-Events im gleichen Zeitraum, um zu finden, wann der Account entfernt wurde.

Vorher ist leer bei einem Update-Event. Der Handler, der das Webhook gefeuert hat, hat keinen before-Payload mitgegeben — der Audit-Logger sieht dann nur den neuen Stand. Item-CRUD ueber /api/backend/item liefert immer Vorher/Nachher; manche aelteren Plugin-Endpunkte liefern nur den neuen Stand. Bei Bedarf fuer ein bestimmtes Event Issue oeffnen.

Severity-Badge in der falschen Farbe. Browser-Cache. Hard-Reload (Cmd+Shift+R / Ctrl+Shift+R). Severity wird server-seitig aus dem Event-Typ berechnet und ist fix, sobald der Eintrag geschrieben ist.

Chain bricht direkt nach einem Datenbank-Restore. DB-Restores koennen TIMESTAMP-Genauigkeit oder Charset aendern — das aendert die Bytes, die in den Hash eingehen. Den Restore in einem frischen audit.chain_reset-Event dokumentieren und eine neue Chain starten.

Siehe auch

  • Task-Manager — der Scheduler, der den taeglichen Verifier und den Archivierer ausfuehrt.
  • Benutzer — Audit-User-IDs den Konten zuordnen.
  • API-Keys — jede Key-Mutation wird mit Critical-Severity geloggt.
  • Update-Manager — Deploys, Migrationen und Plugin-Install/Deinstallation feeden den Audit-Log.