Zum Inhalt

🔒 Sicherheits-Konzept: Mittlere Findings Behebung

Datum: 24. Februar 2026
Scope: Behebung aller MITTEL-Priority Findings (#8-#15) aus dem Security Audit Report
Status: Konzeptphase - Vor Implementierung


📋 Übersicht

Dieses Dokument beschreibt die geplanten Maßnahmen zur Behebung der mittleren Sicherheitsrisiken. Die Implementierung erfolgt in 3 Phasen mit klaren Prioritäten.

Findings im Scope

  • ✅ #7: SharedPreferences Encryption (HOCH) - BEREITS BEHOBEN
  • 🔧 #8: Certificate Pinning (MITTEL)
  • 🔧 #9: ERP Firestore Rules härten (MITTEL)
  • 🔧 #10: JSON.parse Error Handling (MITTEL)
  • 🔧 #11: Content-Type Validierung bei Uploads (MITTEL)
  • 🔧 #12: CORS-Konfiguration Review (MITTEL)
  • 🔧 #13: SQL Injection Protection bei Connector Templates (MITTEL)
  • 🔧 #14: Session Management (MITTEL)
  • 🔧 #15: Error Handling bei Firestore Queries (MITTEL)

🎯 PHASE 1: Backend Security (Cloud Functions & Firestore)

Zeitaufwand: 3-4 Stunden
Priorität: HOCH (serverseitige Sicherheit)

1.1 JSON.parse Error Handling (#10)

Problem:

// UNSICHER - Kann bei korrupten Daten crashen
credentials = JSON.parse(version.payload.data.toString('utf8'));

Betroffene Dateien: - functions/src/jobs/management/updateJob.js (Zeile 96) - functions/src/jobs/executor/executeJobHttp.js (Zeile 113) - functions/src/jobs/management/getJobCredentials.js (Zeile 60) - functions/src/jobs/executor/executeJob.js (Zeile 76) - functions/src/jobs/management/testJob.js (Zeile 44) - functions/src/utils/connector_utils.js (Zeile 19) - functions/src/connectors/connector_management.js (Zeilen 233, 367, 444)

Lösung:

  1. Utility-Funktion erstellen: functions/src/utils/json_utils.js

    const functions = require('firebase-functions');
    
    /**
     * Sicheres JSON Parsing mit Error Handling
     * @param {string} jsonString - Der zu parsende JSON String
     * @param {string} context - Kontext für Fehlermeldung (z.B. "credentials")
     * @returns {Object} Geparstes Object
     * @throws {functions.https.HttpsError} Bei Parsing-Fehler
     */
    function safeJsonParse(jsonString, context = 'data') {
      try {
        // Validierung: String muss vorhanden sein
        if (!jsonString || typeof jsonString !== 'string') {
          throw new Error(`Invalid input: Expected string, got ${typeof jsonString}`);
        }
    
        // Validierung: String darf nicht leer sein
        if (jsonString.trim().length === 0) {
          throw new Error('Empty JSON string');
        }
    
        const parsed = JSON.parse(jsonString);
    
        // Validierung: Ergebnis muss ein Object sein
        if (!parsed || typeof parsed !== 'object') {
          throw new Error(`Invalid JSON structure: Expected object, got ${typeof parsed}`);
        }
    
        return parsed;
      } catch (error) {
        console.error(`[SECURITY] Failed to parse ${context}:`, error.message);
        throw new functions.https.HttpsError(
          'invalid-argument',
          `Invalid ${context} format: ${error.message}`
        );
      }
    }
    
    /**
     * Sicheres Parsen von Secret Manager Payloads
     * @param {Object} version - Secret Manager Version Object
     * @param {string} secretName - Name des Secrets für Logging
     * @returns {Object} Credentials Object
     */
    function parseSecretPayload(version, secretName = 'secret') {
      if (!version || !version.payload || !version.payload.data) {
        throw new functions.https.HttpsError(
          'internal',
          `Secret ${secretName} has invalid structure`
        );
      }
    
      const jsonString = version.payload.data.toString('utf8');
      return safeJsonParse(jsonString, `secret:${secretName}`);
    }
    
    module.exports = {
      safeJsonParse,
      parseSecretPayload
    };
    

  2. Alle betroffenen Dateien aktualisieren

    // Vorher:
    credentials = JSON.parse(version.payload.data.toString('utf8'));
    
    // Nachher:
    const { parseSecretPayload } = require('../utils/json_utils');
    credentials = parseSecretPayload(version, 'connector-credentials');
    

Erwartete Verbesserung: - ✅ Keine unbehandelten Exceptions bei korrupten Secrets - ✅ Klare Fehlermeldungen für Debugging - ✅ Security Logging bei Parse-Fehlern - ✅ Konsistentes Error Handling über alle Functions


1.2 ERP Firestore Rules härten (#9)

Problem:

match /{document=**} {
  allow read, write: if isAuthenticated();
}
Fallback-Regel ist zu permissiv - neue Collections haben automatisch vollen Zugriff.

Betroffene Datei: - firestore.rules (Zeilen 330-337)

Lösung:

  1. Fallback-Regel ändern (Secure by Default):

    // ============================================================================
    // FALLBACK: Explizit ALLES verbieten (Secure by Default)
    // ============================================================================
    // Jede Collection muss explizit freigegeben werden.
    // Dies verhindert versehentliche Datenlecks bei neuen Collections.
    match /{document=**} {
      allow read, write: if false;
    }
    

  2. Fehlende Collections explizit freigeben

Analyse der aktuellen Rules: - ✅ system_settings: Explizit definiert (read: auth, write: admin) - ✅ users: Explizit definiert (komplex) - ✅ customers: Explizit definiert (permissions-basiert) - ✅ articles: Explizit definiert (permissions-basiert) - ✅ orders: Explizit definiert (permissions-basiert) - ✅ customerUsers: Explizit definiert (admin only) - ✅ articleCategories: Explizit definiert - ✅ customerCategorys: Explizit definiert - ✅ notifications: Explizit definiert - ✅ notificationGroups: Explizit definiert - ⚠️ Potentiell fehlend: - jobs & jobExecutions (via Cloud Functions) - statistics (via Cloud Functions) - connectorSettings (via Cloud Functions) - deviceTokens (Push Notifications) - auditLogs (falls vorhanden)

  1. Neue explizite Regeln hinzufügen:
    // ============================================================================
    // JOBS - Nur Cloud Functions haben Zugriff
    // ============================================================================
    // Jobs werden ausschließlich von Cloud Functions verwaltet.
    // Client-Apps haben keinen direkten Zugriff.
    match /jobs/{jobId} {
      allow read: if isAdmin(); // Admins können Jobs einsehen
      allow write: if false; // Nur Cloud Functions (Admin SDK)
    
      match /jobExecutions/{executionId} {
        allow read: if isAdmin();
        allow write: if false;
      }
    }
    
    // ============================================================================
    // STATISTICS - Nur Lese-Zugriff für authentifizierte User
    // ============================================================================
    match /statistics/{statisticId} {
      allow read: if isAuthenticated();
      allow write: if false; // Nur Cloud Functions
    }
    
    match /statistics/{statisticId}/{subcollection=**} {
      allow read: if isAuthenticated();
      allow write: if false;
    }
    
    // ============================================================================
    // CONNECTOR SETTINGS - Nur Admin-Zugriff
    // ============================================================================
    // Connector-Credentials liegen in Secret Manager, nicht in Firestore
    match /connectorSettings/{settingId} {
      allow read: if isAdmin();
      allow write: if isAdmin(); // Nur für Connector-Konfiguration, NICHT Credentials
    }
    
    // ============================================================================
    // DEVICE TOKENS - Für Push Notifications
    // ============================================================================
    match /deviceTokens/{tokenId} {
      allow read: if isAuthenticated();
      // User kann eigenes Device Token registrieren
      allow create, update: if isAuthenticated() && request.auth.uid == request.resource.data.userId;
      allow delete: if isAuthenticated() && request.auth.uid == resource.data.userId;
    }
    
    // ============================================================================
    // AUDIT LOGS - Nur Lesen für SuperAdmin
    // ============================================================================
    match /auditLogs/{logId} {
      allow read: if isSuperAdmin();
      allow write: if false; // Nur Cloud Functions
    }
    

Implementierungsschritte: 1. Backup der aktuellen firestore.rules erstellen 2. Fallback-Regel auf if false ändern 3. Neue explizite Regeln hinzufügen 4. Deployment testen (zuerst in Dev-Umgebung!) 5. Mit firebase emulators:start lokal testen 6. Production Deployment

Erwartete Verbesserung: - ✅ Secure by Default - neue Collections sind standardmäßig geschützt - ✅ Keine versehentlichen Datenlecks - ✅ Explizite Dokumentation aller zugänglichen Collections - ✅ Auditable Security Rules


1.3 CORS-Konfiguration Review (#12)

Betroffene Dateien: - cors.json (Root-Level) - apps/erp_system/cors.json - apps/shop_system/cors.json - apps/shop_system/cors-config.json

Aktuelle Konfiguration (Root):

{
  "origin": ["https://es-dev-2-d4e1a.web.app", "https://es-dev-2-d4e1a.firebaseapp.com", "http://localhost:8080"],
  "method": ["GET"],
  "responseHeader": ["Content-Type"],
  "maxAgeSeconds": 3600
}

Probleme: - ⚠️ Nur GET erlaubt - was ist mit POST für Uploads? - ⚠️ Development URLs in Production Config? - ⚠️ Keine Unterscheidung zwischen Dev/Staging/Prod

Lösung:

  1. Umgebungs-spezifische CORS Configs:

cors.dev.json:

[
  {
    "origin": [
      "http://localhost:8080",
      "http://localhost:5000",
      "https://es-dev-2-d4e1a.web.app",
      "https://es-dev-2-d4e1a.firebaseapp.com"
    ],
    "method": ["GET", "HEAD", "PUT", "POST", "DELETE"],
    "responseHeader": ["Content-Type", "Authorization"],
    "maxAgeSeconds": 3600
  }
]

cors.prod.json:

[
  {
    "origin": [
      "https://your-production-domain.com"
    ],
    "method": ["GET", "HEAD", "PUT", "POST"],
    "responseHeader": ["Content-Type", "Authorization"],
    "maxAgeSeconds": 86400
  }
]

  1. Deployment Script anpassen:
    #!/bin/bash
    # deploy_storage_cors.sh
    
    ENV=${1:-dev}  # dev, staging, prod
    
    if [ -f "cors.$ENV.json" ]; then
      echo "Deploying CORS config for $ENV environment..."
      gsutil cors set cors.$ENV.json gs://your-bucket-name.appspot.com
    else
      echo "Error: cors.$ENV.json not found!"
      exit 1
    fi
    

Erwartete Verbesserung: - ✅ Produktions-Umgebung akzeptiert nur Production-Domains - ✅ Minimal notwendige HTTP-Methoden pro Umgebung - ✅ Keine Wildcard-Origins - ✅ Getrennte Configs für Dev/Staging/Prod


🎯 PHASE 2: Client Security (Flutter Apps)

Zeitaufwand: 4-5 Stunden
Priorität: MITTEL

2.1 Certificate Pinning (#8)

Problem:

final Dio _dio = Dio();
await _dio.download(url, path);
Kein Certificate Pinning - anfällig für MITM-Angriffe.

Betroffene Dateien: - apps/shop_system/lib/services/mobile/document_download_service.dart

Lösung:

Option A: SSL Certificate Pinning mit dio (Empfohlen)

  1. Certificate Hash ermitteln:

    # Für Firebase Storage
    openssl s_client -connect firebasestorage.googleapis.com:443 < /dev/null 2>/dev/null | \
      openssl x509 -fingerprint -sha256 -noout -in /dev/stdin
    

  2. Pinning Service erstellen: lib/services/ssl_pinning_service.dart

    import 'dart:io';
    import 'package:dio/dio.dart';
    import 'package:dio/io.dart';
    
    class SSLPinningConfig {
      // 🔒 SECURITY: Firebase Storage Certificate Fingerprints
      // Diese Hashes müssen aktualisiert werden wenn Firebase Certificates erneuert (ca. alle 1-2 Jahre)
      // Letztes Update: 24. Februar 2026
      static const List<String> _firebaseStoragePins = [
        // Primary Certificate
        'AABBCCDD...', // SHA-256 Fingerprint
        // Fallback Certificate (für graceful rotation)
        'EEFFGGHH...', // SHA-256 Fingerprint
      ];
    
      static Dio createSecureDio() {
        final dio = Dio();
    
        // Custom HttpClient mit Certificate Pinning
        (dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
          final client = HttpClient();
    
          client.badCertificateCallback = (cert, host, port) {
            // SECURITY: Nur für Firebase Storage Hosts pinnen
            if (!host.contains('firebasestorage.googleapis.com') &&
                !host.contains('storage.googleapis.com')) {
              // Für andere Hosts: Standard SSL Validierung
              return false;
            }
    
            // Certificate Fingerprint prüfen
            final certSHA256 = cert.sha256.toString();
    
            if (_firebaseStoragePins.contains(certSHA256)) {
              return true; // Certificate ist gepinnt ✅
            }
    
            // SECURITY ALERT: Certificate nicht gepinnt!
            print('⚠️ [SECURITY] SSL Certificate Pinning fehlgeschlagen!');
            print('Host: $host');
            print('Expected: ${_firebaseStoragePins.join(', ')}');
            print('Got: $certSHA256');
    
            return false; // Verbindung ablehnen
          };
    
          return client;
        };
    
        return dio;
      }
    }
    

  3. Document Download Service aktualisieren:

    import 'package:dio/dio.dart';
    import '../ssl_pinning_service.dart';
    
    class MobileDocumentDownloadService implements DocumentDownloadService {
      // 🔒 SECURITY: Dio mit SSL Certificate Pinning
      final Dio _dio = SSLPinningConfig.createSecureDio();
    
      // Rest bleibt gleich...
    }
    

Option B: Ohne Pinning, aber mit strikter SSL Validierung

import 'dart:io';

class SSLConfig {
  static Dio createSecureDio() {
    final dio = Dio();

    (dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
      final client = HttpClient();

      // Strikte SSL Validierung (kein badCertificateCallback)
      // Nur valide, vertrauenswürdige Zertifikate werden akzeptiert
      client.badCertificateCallback = null;

      return client;
    };

    return dio;
  }
}

Empfehlung: - Production: Option A (Certificate Pinning für maximale Sicherheit) - Development: Option B (vereinfacht Testing mit lokalen Zertifikaten)

Certificate Rotation Prozess: 1. Monitoring: Automatische Alerts 60 Tage vor Certificate-Ablauf 2. Update: Neue Certificate Hashes in App-Update einbauen 3. Overlap: Beide Hashes (alt & neu) für 30 Tage parallel akzeptieren 4. Cleanup: Alte Hashes nach erfolgreicher User-Migration entfernen

Erwartete Verbesserung: - ✅ Schutz vor MITM-Angriffen auf Firebase Storage - ✅ Garantie der Server-Authentizität - ✅ Compliance mit Security Best Practices


2.2 Session Management (#14)

Problem: Keine sichtbare Session Timeout Konfiguration oder Token Refresh Logik.

Analyse benötigt: - Wo wird Firebase Auth genutzt? - Gibt es einen AuthService? - Wie wird Token Expiration gehandhabt?

Lösung:

  1. Auth Service erweitern (falls vorhanden): lib/services/auth_service.dart

    import 'package:firebase_auth/firebase_auth.dart';
    
    class AuthService {
      final FirebaseAuth _auth = FirebaseAuth.instance;
    
      // 🔒 SECURITY: Session Timeout Configuration
      static const Duration _sessionTimeout = Duration(hours: 12);
      static const Duration _tokenRefreshThreshold = Duration(minutes: 5);
    
      /// Prüft ob das Token bald abläuft und erneuert es proaktiv
      Future<void> refreshTokenIfNeeded() async {
        final user = _auth.currentUser;
        if (user == null) return;
    
        try {
          final token = await user.getIdTokenResult();
          final expirationTime = token.expirationTime;
    
          if (expirationTime == null) {
            print('⚠️ [AUTH] Token expiration time unknown');
            return;
          }
    
          final timeUntilExpiration = expirationTime.difference(DateTime.now());
    
          // Token läuft in weniger als 5 Minuten ab? → Refresh
          if (timeUntilExpiration < _tokenRefreshThreshold) {
            print('🔄 [AUTH] Refreshing token (expires in ${timeUntilExpiration.inMinutes}min)');
            await user.getIdToken(true); // Force refresh
            print('✅ [AUTH] Token refreshed successfully');
          }
        } catch (e) {
          print('❌ [AUTH] Token refresh failed: $e');
          // Bei kritischen Fehlern: User ausloggen
          if (e.toString().contains('network-request-failed')) {
            // Netzwerkfehler: Nicht ausloggen
          } else {
            // Andere Fehler: Sicherheitshalber ausloggen
            await signOut();
          }
        }
      }
    
      /// Prüft ob die Session abgelaufen ist (inaktiv zu lange)
      Future<bool> isSessionValid() async {
        final user = _auth.currentUser;
        if (user == null) return false;
    
        final metadata = user.metadata;
        final lastSignIn = metadata.lastSignInTime;
    
        if (lastSignIn == null) return false;
    
        final sessionAge = DateTime.now().difference(lastSignIn);
    
        if (sessionAge > _sessionTimeout) {
          print('⚠️ [AUTH] Session expired (${sessionAge.inHours}h)');
          await signOut();
          return false;
        }
    
        return true;
      }
    
      Future<void> signOut() async {
        await _auth.signOut();
      }
    }
    

  2. App Lifecycle Hook: In main.dart oder zentralem App-Widget

    class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
      @override
      void initState() {
        super.initState();
        WidgetsBinding.instance.addObserver(this);
    
        // Initial Token Refresh
        _refreshTokenPeriodically();
      }
    
      void _refreshTokenPeriodically() {
        Timer.periodic(Duration(minutes: 4), (timer) {
          final authService = getIt<AuthService>();
          authService.refreshTokenIfNeeded();
        });
      }
    
      @override
      void didChangeAppLifecycleState(AppLifecycleState state) {
        if (state == AppLifecycleState.resumed) {
          // App kommt in Vordergrund: Session validieren
          final authService = getIt<AuthService>();
          authService.isSessionValid();
          authService.refreshTokenIfNeeded();
        }
      }
    }
    

Erwartete Verbesserung: - ✅ Proaktive Token-Erneuerung verhindert Ablauf während Nutzung - ✅ Session Timeout erhöht Security bei Device-Verlust - ✅ Automatisches Logout bei langer Inaktivität - ✅ Bessere User Experience (keine plötzlichen Auth-Fehler)


2.3 Error Handling bei Firestore Queries (#15)

Problem: Viele Firestore-Queries haben kein explizites Timeout oder Error Handling.

Aktueller Stand:extensions/firestore_extensions.dart hat bereits Timeout implementiert

Lösung:

  1. Prüfen ob Extensions genutzt werden:

    # Suche nach getSafe() Nutzung
    grep -r "getSafe" apps/*/lib
    

  2. Falls nicht konsequent genutzt: Migration

Erstelle Linter-Regel oder Code-Search um unsichere Queries zu finden:

// UNSICHER - ohne Timeout
final doc = await FirebaseFirestore.instance.collection('test').doc('id').get();

// SICHER - mit Timeout
final doc = await FirebaseFirestore.instance.collection('test').doc('id').getSafe();

  1. Globale Firestore Konfiguration:
    // lib/config/firestore_config.dart
    import 'package:cloud_firestore/cloud_firestore.dart';
    
    class FirestoreConfig {
      static void initialize() {
        FirebaseFirestore.instance.settings = Settings(
          persistenceEnabled: true,
          cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED,
          // Timeout auf Firestore-Verbindungs-Ebene (experimentell)
        );
      }
    }
    

Implementierungsschritte: 1. Audit aller Firestore-Queries (automatisiert via grep) 2. Migration zu getSafe() Extension 3. Code Review Guideline: Nur noch getSafe() nutzen 4. Optional: Custom Lint Rule erstellen

Erwartete Verbesserung: - ✅ Keine hängenden Queries bei Netzwerkproblemen - ✅ Konsistentes Error Handling - ✅ Bessere User Experience (Loading States enden)


🎯 PHASE 3: Connector & Template Security

Zeitaufwand: 2-3 Stunden
Priorität: NIEDRIG (betrifft nur interne Connector-Entwicklung)

3.1 SQL Injection Protection bei Connector Templates (#13)

Betroffene Dateien: - functions/src/connectors/templates/rest_api_template.js - Alle Custom Connector Implementierungen

Problem: Wenn Connectoren SQL-Datenbanken anbinden, fehlt dokumentierte Best Practice für Prepared Statements.

Lösung:

  1. SQL Connector Template erstellen: functions/src/connectors/templates/sql_connector_template.js

    /**
     * SQL Connector Template mit Security Best Practices
     * 
     * SECURITY REQUIREMENTS:
     * 1. IMMER Prepared Statements nutzen (NIEMALS String Concatenation)
     * 2. Input Validation für alle Parameter
     * 3. Least Privilege: Database User hat nur SELECT Rechte
     * 4. Connection Pooling mit Timeouts
     * 5. Error Messages dürfen keine DB-Struktur leaken
     */
    
    const mysql = require('mysql2/promise');
    
    class SQLConnector {
      constructor(credentials) {
        this.pool = mysql.createPool({
          host: credentials.host,
          user: credentials.user,
          password: credentials.password,
          database: credentials.database,
          waitForConnections: true,
          connectionLimit: 10,
          queueLimit: 0,
          connectTimeout: 10000,
          // SECURITY: SSL für Produktions-DBs erzwingen
          ssl: credentials.ssl_enabled ? {
            rejectUnauthorized: true
          } : false
        });
      }
    
      /**
       * Sichere Query-Ausführung mit Prepared Statements
       * @param {string} query - SQL Query mit ? Platzhaltern
       * @param {Array} params - Parameter für Query
       * @returns {Promise<Array>} Query Ergebnisse
       */
      async executeQuery(query, params = []) {
        try {
          // SECURITY: Validiere Query-Typ (nur SELECT erlaubt für Datenabruf)
          const queryType = query.trim().toUpperCase().split(' ')[0];
          if (!['SELECT'].includes(queryType)) {
            throw new Error(`Query type ${queryType} not allowed for data connector`);
          }
    
          // SECURITY: Prepared Statement schützt vor SQL Injection
          const [rows] = await this.pool.execute(query, params);
    
          return rows;
        } catch (error) {
          // SECURITY: Sanitize error message (keine DB-Internals leaken)
          console.error('[SQL Connector] Query failed:', error.message);
          throw new Error('Database query failed. Check connector configuration.');
        }
      }
    
      /**
       * EXAMPLE: Customer Sync
       */
      async syncCustomers(lastSyncTimestamp) {
        // 🔒 RICHTIG: Prepared Statement
        const query = `
          SELECT id, name, email, updated_at 
          FROM customers 
          WHERE updated_at > ? 
          ORDER BY updated_at ASC 
          LIMIT 1000
        `;
    
        const customers = await this.executeQuery(query, [lastSyncTimestamp]);
    
        return customers;
      }
    
      /**
       * ❌ FALSCH - NIEMALS SO MACHEN!
       */
      async syncCustomersUnsafe(lastSyncTimestamp) {
        // SQL INJECTION MÖGLICH!
        const query = `SELECT * FROM customers WHERE updated_at > '${lastSyncTimestamp}'`;
        // Attacker könnte lastSyncTimestamp = "'; DROP TABLE customers; --" setzen
      }
    
      async close() {
        await this.pool.end();
      }
    }
    
    module.exports = SQLConnector;
    

  2. Security Checklist für Connector-Entwickler: functions/src/connectors/SECURITY_CHECKLIST.md

    # Connector Security Checklist
    
    Vor Deployment eines neuen Connectors ALLE Punkte prüfen:
    
    ## SQL-basierte Connectoren
    - [ ] Prepared Statements für ALLE Queries
    - [ ] Kein String Concatenation für SQL Queries
    - [ ] Database User hat nur SELECT Rechte (kein INSERT/UPDATE/DELETE)
    - [ ] SSL/TLS Verbindung zur Datenbank
    - [ ] Connection Timeout konfiguriert (max 10 Sekunden)
    - [ ] Error Messages leaken keine DB-Struktur
    - [ ] Query LIMIT implementiert (max 10.000 Datensätze)
    
    ## API-basierte Connectoren
    - [ ] API Key/Token werden in Secret Manager gespeichert
    - [ ] HTTPS für alle API Calls (kein HTTP)
    - [ ] Request Timeout konfiguriert (max 30 Sekunden)
    - [ ] Rate Limiting beachtet
    - [ ] Input Validation für alle Parameter
    - [ ] API Fehler werden nicht 1:1 an Client weitergegeben
    
    ## Allgemein
    - [ ] Credentials niemals im Code (nur Secret Manager)
    - [ ] Logging ohne sensible Daten (PII)
    - [ ] Error Handling implementiert
    - [ ] Unit Tests für Connector geschrieben
    

Erwartete Verbesserung: - ✅ Verhindert SQL Injection in Custom Connectoren - ✅ Dokumentierte Best Practices für Entwickler - ✅ Template als Startpunkt für neue Connectoren


3.2 Content-Type Validierung bei Uploads (#11)

Betroffene Datei: - storage.rules

Problem: Storage Rules validieren nur bei article_documents die Dateigröße, nicht aber Content-Types konsistent.

Lösung:

Siehe Finding #2 aus dem Security Audit Report - bereits dokumentiert.

Kurzversion:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {

    // Helper: Prüft ob customerId zum User gehört (via Firestore)
    function hasCustomerAccess(customerId) {
      return request.auth != null && 
        firestore.get(/databases/(default)/documents/customerUsers/$(request.auth.uid)).data.customerId == customerId;
    }

    // Artikel-Dokumente
    match /article_documents/{customerId}/{articleId}/{documentId} {
      allow read: if request.auth != null && hasCustomerAccess(customerId);
      allow write: if request.auth != null 
        && hasCustomerAccess(customerId)
        && request.resource.size < 10 * 1024 * 1024  // Max 10 MB
        && request.resource.contentType.matches('application/pdf|image/.*');  // Nur PDF und Bilder
    }

    // Kunden-Dokumente
    match /customer_documents/{customerId}/{documentId} {
      allow read: if request.auth != null && hasCustomerAccess(customerId);
      allow write: if request.auth != null 
        && hasCustomerAccess(customerId)
        && request.resource.size < 20 * 1024 * 1024  // Max 20 MB
        && request.resource.contentType.matches('application/pdf|image/.*|application/vnd\\..*');  // PDF, Bilder, Office
    }

    // Feed Attachments
    match /feed_attachments/{customerId}/{feedId}/{attachmentId} {
      allow read: if request.auth != null && hasCustomerAccess(customerId);
      allow write: if request.auth != null 
        && hasCustomerAccess(customerId)
        && request.resource.size < 50 * 1024 * 1024  // Max 50 MB
        && request.resource.contentType.matches('application/pdf|image/.*|video/.*|audio/.*');  // Multimedia
    }

    // Explizit: Kein Zugriff auf unbekannte Pfade
    match /{allPaths=**} {
      allow read, write: if false;
    }
  }
}

Implementierungsschritte: 1. Backup der aktuellen storage.rules 2. Neue Rules mit Content-Type Validierung erstellen 3. Testing in Emulator 4. Deployment

Erwartete Verbesserung: - ✅ Kein Upload von Malware/Executables - ✅ Konsistente File-Type Beschränkungen - ✅ Schutz vor Storage-Missbrauch


📊 Zusammenfassung & Zeitplan

Geschätzter Gesamtaufwand: 9-12 Stunden

Phase Aufwand Priorität Findings
Phase 1: Backend Security 3-4h HOCH #9, #10, #12
Phase 2: Client Security 4-5h MITTEL #8, #14, #15
Phase 3: Connector Security 2-3h NIEDRIG #11, #13

Empfohlene Reihenfolge:

Woche 1: 1. ✅ JSON.parse Error Handling (#10) - 1h 2. ✅ ERP Firestore Rules härten (#9) - 1.5h 3. ✅ CORS Review & Update (#12) - 1h

Woche 2: 4. ✅ Certificate Pinning (#8) - 2h 5. ✅ Session Management (#14) - 2h

Woche 3: 6. ✅ Content-Type Validierung (#11) - 1.5h 7. ✅ SQL Injection Protection Template (#13) - 1.5h 8. ✅ Firestore Error Handling Audit (#15) - 1h


🧪 Testing-Strategie

Automatisierte Tests

  • Firestore Rules: firebase emulators:start + Unit Tests
  • Storage Rules: Emulator + Upload Tests
  • Cloud Functions: Jest Unit Tests für JSON Parsing
  • Flutter: Widget Tests für Auth Flows

Manuelle Tests

  • Certificate Pinning: MITM Proxy (Burp Suite) - Verbindung muss fehlschlagen
  • Session Timeout: App 12h+ im Hintergrund lassen
  • CORS: cURL Tests von verschiedenen Origins

Security Audit (nach Implementierung)

  • [ ] Erneuter Durchlauf aller MITTEL-Findings
  • [ ] Penetration Testing der gehärteten Firestore Rules
  • [ ] Code Review durch zweite Person
  • [ ] Update des Security Audit Reports

📝 Dokumentation

Nach Implementierung zu aktualisieren: - ✅ SECURITY_AUDIT_REPORT.md - Status Update auf "BEHOBEN" - ✅ README.md - Security Features dokumentieren - ✅ functions/README.md - JSON Utils & Connector Security - ✅ Inline Code Comments mit "SECURITY:" Marker


❓ Offene Fragen für Implementierung

  1. Firebase Hosting Umgebungen:
  2. Haben wir separate Dev/Staging/Prod Firebase Projects?
  3. Wie erfolgt das Deployment? (CI/CD Pipeline?)

  4. Certificate Pinning:

  5. Müssen wir auch ERP-System anpassen (gleicher Code?)
  6. Wie handhaben wir Certificate Rotation bei 10.000+ Nutzern?

  7. Session Management:

  8. Existiert bereits ein AuthService in ERP/Shop?
  9. Welche Session-Dauer ist gewünscht? (Vorschlag: 12h)

  10. Testing:

  11. Haben wir eine bestehende Test-Suite?
  12. Wer führt Security Testing durch?

🚀 Nächste Schritte

Zur Freigabe benötigt: 1. Review dieses Konzepts 2. Priorisierung bestätigen oder anpassen 3. Klärung der offenen Fragen 4. Go/No-Go für Implementierung

Nach Freigabe: 1. Branch erstellen: security/mittel-findings 2. Implementation gemäß Phasenplan 3. Testing nach jedem Phase 4. Pull Request mit detailliertem Changelog 5. Security Audit Report aktualisieren


Konzept erstellt am: 24. Februar 2026
Bereit für Review & Implementierung