Zum Inhalt springen
flutter 2026-03-26

Ich bin zwar nur Entwickler und kein DSGVO-Experte... aber

Ein Claude-Code-Skill prüft Flutter-Code auf DSGVO-Konformität vor jedem Merge. Was er gefunden hat — und was ich daraus gemacht habe.


Ich bin zwar nur Entwickler und kein DSGVO-Experte... aber

Ich bin zwar nur Entwickler und kein DSGVO-Experte… aber

TL;DR: Ein eigener Claude-Code-Skill prüft Flutter-Code auf DSGVO-Konformität vor jedem Merge. Ergebnis: Sentry hat personenbezogene Daten an US-Server übertragen, die Consent-Implementierung funktionierte nur auf iOS, der Consent-Zustand lag in einer globalen Variable, und Apples ATT war unnötig, weil kein SDK die IDFA nutzte. Hier der Skill, die Befunde und die Code-Verbesserungen.

Dieser Artikel setzt Flutter-Erfahrung (BLoC, freezed) und ein Grundverständnis der DSGVO (EU-Verordnung 2016/679) voraus. Juristisches Know-how ist nicht nötig — genau darum geht es.

Das Problem

Freiberuflich, Health-App, deutscher Kunde, Flutter, B2C, vor dem Launch, Gesundheitsdaten, deutsche Nutzer — hohes DSGVO-Komplexitätsniveau.

Die Consent-Implementierung bestand aus einer einzigen Aktion: Apples ATT-Prompt auf iOS. Für Android-Nutzer gab es nichts. Das ATT-Ergebnis wurde nicht vernünftig persistiert, sondern existierte als globale Variable in globals.dart. Dazu lief Sentry mit sendDefaultPii: true und attachScreenshot: true — und übertrug aktiv Nutzerdaten und Screenshots von Gesundheits-Screens an US-Server.

Das war keine Absicht — es hat sich über Sprints angesammelt, wie es eben passiert. Verschiedene Tracking-SDKs und vergessene Debug-Flags.

Als Entwickler, der diesen Code ausliefert, brauchte ich einen systematischen Ansatz, um Datenschutzprobleme zu identifizieren. Nicht einmalig, sondern bei jedem Branch.

Der Ansatz: ein DSGVO-Audit-Skill für Claude Code

Die Idee: eine strukturierte Prompt-Datei, die Claude Code beibringt, Quellcode systematisch gegen die DSGVO (EU-Verordnung 2016/679) zu prüfen. Statt jede Branch manuell auf PII-Felder, Consent-Lücken und SDK-Konfigurationen zu kontrollieren, erledigt das ein automatisierter, KI-gestützter Prozess mit Verweis auf konkrete Gesetzesartikel.

“Claude Code” hat sogenannte “Skills” — Prompt-Dateien, die der KI domänenspezifisches Wissen für bestimmte Aufgaben geben. Ein Skill namens /gdpr-audit macht Claude zum systematischen DSGVO-Reviewer.

Statt allgemeine Empfehlungen zu Consent-Flows zu geben, untersucht der Skill systematisch die gesamte Codebasis.

Erst werden alle Datenflüsse inventarisiert: PII-Felder, Auth-Tokens, Device-IDs, Gesundheitsbegriffe, Analytics-Events, Error-Reporting-Konfigurationen. Alle Konfigurationsdateien werden geprüft.

Dann werden Befunde an konkrete DSGVO-Artikel gekoppelt:

BereichDSGVO-ArtikelWas geprüft wird
ConsentArt. 6, 7, 9Granularität? Widerrufbarkeit? Plattformübergreifend? Vor Verarbeitung?
BetroffenenrechteArt. 15-22Auskunft, Berichtigung, Löschung, Export möglich?
SicherheitArt. 32PII in Logs? Tokens in Secure Storage? Screenshots mit Gesundheitsdaten?
DatenminimierungArt. 5(1)(c)(e)Unnötige Permissions? Aufbewahrungsfristen?
AuftragsverarbeitungArt. 28, 44-49AVVs vorhanden? Transfermechanismen für US-SDKs?
Push-BenachrichtigungenTokens nach Löschung deregistriert? PII in Payloads?

Der Output: Dateipfade, Zeilennummern, DSGVO-Artikelreferenzen mit Schweregrad, Risikobewertung und konkreten Maßnahmenvorschlägen pro Befund.

Die Skill-Definition umfasst rund 195 Zeilen Markdown — im Kern eine Checkliste, die aber manuell bei jedem Branch kaum jemand durchgehen würde.

Was er gefunden hat

/gdpr-audit auf dem Feature-Branch ausgeführt. Mehrere kritische Befunde:

Sentry als PII-Schleuser

// Vorher - lib/main.dart
options.sendDefaultPii = true;
options.attachScreenshot = true;
options.screenshotQuality = SentryScreenshotQuality.low;
options.attachViewHierarchy = true;
options.maxRequestBodySize = MaxRequestBodySize.always;

Der Audit flaggte das unter Artikel 32 (Sicherheit der Verarbeitung) und Artikel 9 (besondere Kategorien). Screenshots mit Gesundheitsinhalten an Sentrys US-Infrastruktur. Request Bodies mit Auth-Tokens. View Hierarchy mit Widget-Trees, die Nutzerdaten enthielten.

Die Korrektur:

// Nachher - lib/main.dart
options.sendDefaultPii = false;
options.attachScreenshot = false;
options.maxRequestBodySize = MaxRequestBodySize.never;
options.beforeSend = _stripSensitiveData;

Ein beforeSend-Callback redaktiert Consent-Parameter, Subscription-Keys oder Access-Tokens aus Breadcrumbs:

SentryEvent? _stripSensitiveData(SentryEvent event, Hint hint) {
  final breadcrumbs = event.breadcrumbs?.map((b) {
    if (b.data != null && b.data!.containsKey('url')) {
      final url = b.data!['url']?.toString() ?? '';
      if (url.contains('b2c_cmp_') ||
          url.contains('Subscription-Key') ||
          url.contains('access_token')) {
        final sanitized = Map<String, dynamic>.from(b.data!);
        final uri = Uri.tryParse(url);
        sanitized['url'] = uri != null
            ? '${uri.scheme}://${uri.host}${uri.path}?[REDACTED]'
            : '[REDACTED]';
        return Breadcrumb(
          message: b.message,
          category: b.category,
          type: b.type,
          data: sanitized,
          level: b.level,
          timestamp: b.timestamp,
        );
      }
    }
    return b;
  }).toList();

  event.breadcrumbs = breadcrumbs;
  return event;
}

Consent nur auf iOS, und eine unnötige Abhängigkeit

Die ursprüngliche Implementierung rief AppTrackingTransparency.requestTrackingAuthorization() direkt in main() auf, bevor etwas gerendert wurde. Android-Nutzer bekamen gar keinen Consent-Dialog.

Der Audit zitierte Artikel 7 (Bedingungen für die Einwilligung): Consent muss freiwillig, spezifisch, informiert und plattformübergreifend sein. Aber bei der Prüfung der Datenflüsse fiel noch etwas auf: Kein SDK in der App nutzte die IDFA. Kein Ad-Netzwerk, kein Attributionsdienst, nichts. Die ATT-Abfrage war ein Relikt aus einer früheren Planungsphase.

ATT wurde komplett entfernt — nicht nur der Code, sondern auch das Package aus der pubspec.yaml, die NSUserTrackingUsageDescription aus Info.plist und der iOS-Plattform-Check im Cubit. Ohne die systematische Befragung der Datenflüsse wäre das wohl nicht aufgefallen. ATT war einfach da, also wurde es genutzt.

Ein dedizierter, plattformübergreifender Consent-Mechanismus ersetzt es: ein ConsentCubit mit Bottom Sheet beim ersten Start auf beiden Plattformen.


abstract class ConsentState with _$ConsentState {
  const ConsentState._();

  const factory ConsentState({
    required int prefs,
    required int stats,
    required int market,
    required bool collected,
  }) = _ConsentState;

  bool get isGranted => prefs == 1 && stats == 1 && market == 1;

  factory ConsentState.granted() => const ConsentState(
        prefs: 1, stats: 1, market: 1, collected: true);

  factory ConsentState.denied() => const ConsentState(
        prefs: 0, stats: 0, market: 0, collected: true);
}

Der Consent-State wird in SharedPreferences persistiert, per BlocProvider gescoped und vor dem Login validiert:

Future<void> _collectConsentIfNeeded() async {
  final consentCubit = context.read<ConsentCubit>();
  if (consentCubit.state.collected) return;

  final choice = await showConsentBottomSheet(context);
  if (!mounted) return;

  if (choice == ConsentChoice.acceptAll) {
    await consentCubit.acceptAll();
  } else {
    await consentCubit.onlyNecessary();
  }
}

acceptAll setzt den State direkt. Keine if (_isIOS)-Bedingungen mehr, keine _requestAttAndEmit()-Aufrufe, kein Plattform-Switching. Die gesamte ATT-Logik ist weg, und der Cubit ist auf etwa die Hälfte geschrumpft.

Die globale Variable

Ursprünglich lagen Consent-Parameter in einer globalen Variable in globals.dart. Der Audit identifizierte das als Korrektheits- und Sicherheitsproblem: Jeder Code konnte den Consent-Zustand lesen oder ändern.

Jetzt läuft alles über den Cubit. Consent-Parameter werden über explizite Funktionsparameter an WebView-URLs angehängt, nicht über globalen State.

Was der Audit noch gefunden hat, und was ich inzwischen gefixt habe

Das Wertvollste am Audit ist, was er als fehlend identifiziert. Ein High-Severity-Befund: kein Widerrufsmechanismus (Artikel 7, Absatz 3). Einmal Consent gegeben, war Änderung nur durch Neuinstallation möglich. Die Verordnung sagt: “Der Widerruf der Einwilligung muss so einfach sein wie die Erteilung.”

Das ist inzwischen behoben. In den Datenschutzeinstellungen gibt es jetzt einen “Cookies & Tracking”-Schalter, der über den bestehenden ConsentCubit läuft:

Future<void> _onConsentToggle(bool value) async {
  final cubit = context.read<ConsentCubit>();
  if (!value) {
    await cubit.onlyNecessary();
    return;
  }
  final choice = await showConsentBottomSheet(context);
  if (!mounted) return;
  if (choice == ConsentChoice.acceptAll) {
    await cubit.acceptAll();
  } else {
    await cubit.onlyNecessary();
  }
}

Deaktivieren geht sofort. Reaktivieren öffnet das Consent-Sheet für bewusste Neuzustimmung — wie beim ersten Start.

Ein High-Severity-Befund ist noch offen: Consent-Bündelung (Artikel 7). Nutzer akzeptieren oder lehnen alles zusammen ab. Einzelne Kontrolle über Präferenzen, Statistiken oder Marketing fehlt noch. Das Datenmodell hat schon separate Felder (prefs, stats, market), aber die UI hat noch keine einzelnen Toggles. Steht im Backlog mit Artikelreferenzen.

Wie ich das im Alltag nutze

  1. Feature-Branch mit Änderungen an Analytics, Nutzerdaten, Third-Party-SDKs oder Consent
  2. /gdpr-audit in Claude Code ausführen, bevor der Pull Request aufgeht
  3. Strukturierten Report mit Befunden, Schweregraden und Datei:Zeilen-Referenzen bekommen
  4. Fixen, was in den aktuellen Sprint passt; den Rest dokumentieren

Der Skill erkennt auch Dependency-Upgrades als potenzielle Risiken. Beim Upgrade von sentry_flutter 8.x auf 9.x, firebase_analytics 11.x auf 12.x und flutter_secure_storage 9.x auf 10.x wurden alle drei unter Artikel 28 (Auftragsverarbeitung) geflaggt — mit Empfehlung, Changelogs auf datenschutzrelevante Änderungen zu prüfen. Solche Details rutschen leicht durch, wenn man Build-Fixes macht.

Was das ist und was nicht

Das ersetzt keine Rechtsberatung. Das erstellt keine Auftragsverarbeitungsverträge. Das versteht keine vertraglichen Pflichten oder was der Datenschutzbeauftragte abgesegnet hat.

Was es kann: eine 195-Zeilen-Checkliste auf Basis echter Regulierung, automatisiert gegen Code-Diffs laufen lassen. Es findet mechanische Probleme: das Sentry-Flag, das noch auf true steht, die Plattform ohne Consent-Dialog, den Consent-Zustand in einer globalen Variable.

Für einen Freelancer, der an einer Health-App in Deutschland arbeitet, ist der Unterschied zwischen “hab ich manuell geprüft, sollte passen” und einem dokumentierten Audit-Report mit konkreten Befunden relevant. Die Consent-Implementierung waren ca. 200 Zeilen Dart. Sentry-Härtung hat Code entfernt, nicht hinzugefügt. Die ATT-Abhängigkeit? Komplett eliminiert, inklusive Package, Info.plist-Eintrag und Cubit-Plattform-Check. Weniger Code, weniger Abhängigkeiten, kleinere Angriffsfläche. Nichts davon war schwierig, sobald jemand (oder etwas) auf die konkrete Zeile zeigt und sagt “Artikel 32, Problem.” Das Wissen war nicht das Hindernis. Die Aufmerksamkeit war es.

Häufige Fragen

Ersetzt ein KI-Audit eine rechtliche Prüfung durch einen Anwalt?

Nein. Der Skill findet technische Probleme: falsche Sentry-Flags, fehlende Consent-Dialoge, PII in Logs. Er kann keine Auftragsverarbeitungsverträge bewerten, keine spezifischen rechtlichen Pflichten einordnen und keine Datenschutz-Folgenabschätzung nach Artikel 35 DSGVO durchführen. Er zeigt Code-Probleme. Ob die rechtlich relevant sind, müssen andere klären.

Warum reicht Apples ATT (App Tracking Transparency) nicht als DSGVO-Consent?

ATT betrifft nur die IDFA-Nutzung (Identifier for Advertisers) — ein iOS-exklusives Framework ohne Android-Äquivalent. Es unterscheidet nicht zwischen Statistiken und Marketing. Es erfüllt nicht, was Artikel 7 DSGVO als “freiwillig, spezifisch und informiert” beschreibt. DSGVO-Konformität erfordert einen eigenen Consent-Mechanismus, der auf beiden Plattformen funktioniert. In diesem Fall nutzte ohnehin kein SDK die IDFA — ATT war komplett unnötig.

Welche DSGVO-Artikel sind für App-Entwickler am relevantesten?

Artikel 6 und 7 (Rechtsgrundlage und Einwilligung), Artikel 9 (besondere Kategorien, inkl. Gesundheitsdaten), Artikel 17 (Recht auf Löschung), Artikel 28 (Auftragsverarbeitung — betrifft jedes Third-Party-SDK), und Artikel 32 (Sicherheit der Verarbeitung — betrifft Logging, Error Reporting, Storage).

Funktioniert der Audit-Skill nur für Flutter?

Der /gdpr-audit-Skill prüft Code-Patterns, keine Framework-spezifischen APIs. Die Checkliste gilt für jede Mobile- oder Web-Anwendung. PII-Felder, Tokens und Analytics-Events sind Framework-unabhängig. Nur Dateinamen müssen angepasst werden (z.B. pubspec.yaml vs. package.json).

Weiterführende Ressourcen

Der /gdpr-audit-Skill und die Consent-Implementierung stammen aus einer echten Produktions-App. Audit-Reports, Code-Beispiele und offene Befunde kommen aus dem tatsächlichen Branch-Diff.

KH
Khalit Hartmann Freelance Mobile & Full-Stack Developer