Architekturprüfung / Independent Review
AUDIT // 13.08.2026 // STATISCH

Repository Audit · audioguide-mach

Nicht nur
unfertig.
Unsicher.

Ein System mit brauchbaren Bausteinen, das seine eigene Sicherheitsgrenze untergräbt. Der öffentliche Gateway ist vorhanden; Firestore umgeht ihn trotzdem. Das ist keine Architektur, sondern ein Widerspruch mit Deployment-Skript.

01 / Urteil

Management Summary

Die Anwendung enthält erkennbare Produktionsambitionen: tenant-scoped Gateway, Zod, strukturierte Logs und Tests für Hintergrundjobs. Leider ist davon genau das Sicherheitsversprechen direkt aushebelbar. Öffentliche QR-IDs erlauben an Firestore vorbei den Vollzugriff auf Exponatdaten; parallel betreibt das Repository zwei driftende Backends und eine CI, die Tests dekorativ behandelt.

Freigabeentscheidung

Kein Staging.
Erst Sicherheitsgrenze reparieren.

Die API-Projektion liefert nur freigegebene Felder. Die Firestore-Regeln liefern bei direktem Zugriff den ganzen Datensatz. Damit existiert die Projektion zwar im Code, aber nicht als Zugriffskontrolle.

1 kritische Sicherheitsgrenze

… die effektiv keine ist.

2 Backend-Wahrheiten

Gateway und lokale Mikroservice-Kopien driften bereits auseinander.

0 CI-Testschritte

Build ist kein Qualitätsnachweis. Hier wird er dennoch so behandelt.

505 statisch betrachtete Module

Keine Ausführung, keine Änderungen am geprüften Repository.

02 / Struktur

Architektur mit doppeltem Boden

Der zentrale Functions-Gateway soll die öffentliche Datenform erzwingen. Daneben stehen lokale, kopierte Service-Implementierungen für Entwicklung und Betrieb. Dass beide schon bei Chat-, Medien-, Audio- und Story-Pfaden divergieren, ist kein hypothetisches Wartungsrisiko, sondern der vorprogrammierte nächste Incident.

Beabsichtigter Produktionspfad
Visitor & Admin Frontendspackages/visitor-pwa · packages/admin-panel
Firebase Hosting → Functions Gatewayfunctions/src/index.ts · 177 Routen
Feldprojektion & Tenant-ScopeÖffentliche Daten sollen hier reduziert werden
Firestore / Storage / Jobspersistente Systemgrenzen
Tatsächliche Nebenpfade
Direkter anonymer Firestore-Readumgeht API-Projektion und Freigabegrenze
Lokale Mikroservice-Kopienaudio · chat · exhibit · media · stories
Admin-API als God-Modul1.938 Zeilen · 108 Exporte in einer Datei
Unklare BetriebsquelleJobs-Build ohne versioniertes, deklaratives Deployment

Eine Schicht ist nur eine Sicherheitsgrenze, wenn kein alternativer Pfad sie überspringen kann. Hier überspringt ein öffentlicher Read genau die Schicht, die den Zugriff beschränken soll.

03 / Befunde

Die Dinge, die nicht passieren dürfen

Priorisierung nach Ausnutzbarkeit und möglichem Schaden. Ein schöner Codepfad ist wertlos, wenn der billigere Zugriff daneben offensteht.

P0

Öffentliche Firestore-Reads umgehen die öffentliche Datenprojektion

Anonyme Clients können komplette Dokumente aus exhibits, audioGuides, mediaFiles, stories und stops lesen. Eine öffentliche QR-ID ist damit faktisch ein Schlüssel zum Vollzugriff auf Daten, die der Gateway ausdrücklich begrenzen will.

firestore.rules:37–49,75–86
functions/src/index.ts:357

P1

Branding-Import ist SSRF mit kosmetischem Etikett

Die Validierung beschränkt sich auf HTTP(S). Der Server ruft anschließend eine beliebige URL ab; Redirects, DNS-Auflösung zu privaten/reservierten Netzen und externe CSS-Nachladungen sind nicht robust begrenzt.

tenant.routes.ts:418
branding-extract.service.ts:90,104

P1

CI suggeriert Qualität, führt aber keine Tests aus

Der Pipeline fehlt ein verbindlicher Testschritt. Zusätzlich transpiliert der Functions-Build via esbuild statt einen TypeScript-Check als Qualitätsgrenze durchzusetzen. Grün heißt hier: Dateien ließen sich ungefähr zusammenpacken.

.github/workflows/ci.yml:42
functions/package.json:9

P1

Jobs sind gebaut, aber nicht reproduzierbar ausgerollt

Docker- und Cloud-Build-Pfade erzeugen Images, jedoch ohne versionierte, deklarative Deployment-Definition. GitHub Actions baut ebenfalls, rollt aber nicht aus. Das schafft Betrieb nach Erinnerungsvermögen.

jobs/Dockerfile:11
infra/cloudbuild.yaml:1

P2

Storage ist öffentlich, während die Architektur signierte URLs behauptet

Die Storage-Regeln gewähren öffentlichen Zugriff auf Tenant-Pfade. Das widerspricht dem Sicherheitsmodell der signierten URLs und produziert eine zweite, unkontrollierte Distributionsebene.

storage.rules:10+

P2

Validierung endet ausgerechnet bei Settings als Record<string, unknown>

Der Settings-Endpunkt nimmt eine amorphe Struktur an statt ein Zod-Schema an der Grenze durchzusetzen. Genau dort wird aus einer Typdefinition eine höfliche Empfehlung.

settings.routes.ts:55

P2

Route-Reihenfolge macht einen spezifischen Endpunkt unerreichbar

Die generische Audio-Route /:id steht vor /exhibit/:exhibitId. Routingsysteme lesen von oben nach unten; Überraschung: der konkrete Pfad verliert.

audio.routes.ts:77

P2

CSP, Swagger-CDN und externe Fonts widersprechen sich

Die Content-Security-Policy verhindert beziehungsweise erschwert Ressourcen, die der Code dynamisch von CDN bzw. Google bezieht. Das ist kein Defense-in-Depth-Konzept, sondern Konfigurationsroulette.

ApiDocsPage.tsx:38
tenantInfo.ts:59 · firebase.json:98

P2

Rohdaten aus LLM- und Fehlerpfaden landen ohne Redaction im Log

Prompt- und Fehlerinhalt kann strukturiert geloggt werden, aber ohne zentrale Redaction. Das ist die übliche Art, aus Debugging eine spätere Datenschutzaufgabe zu machen.

pdf routes:210
logger:20

04 / Wartbarkeit

Qualität ist kein Build-Artefakt

Das Repository kennt bessere Muster. Es wendet sie bloß nicht konsequent an. Genau das macht den Befund unangenehmer: Das Wissen ist da, die Architekturdisziplin nicht.

Code-Qualität & Anti-Patterns

  • Admin-Panel: API-God-Modul

    packages/admin-panel/src/lib/api.ts vereint 1.938 Zeilen und 108 Exporte. Verantwortlichkeiten, Verträge und Caching werden dadurch nicht zentralisiert, sondern vernebelt.

  • Typflucht in Jobs

    142 Vorkommen von as any sowie 27 Doppelcasts as unknown as außerhalb der Tests. Das ist kein Grenzadapter, sondern ein institutionalisierter Verzicht auf den Compiler.

  • Promise.allSettled als Fehlerschredder

    Mehrere Pfade sammeln Teilergebnisse und melden trotzdem Erfolg. Besonders bei Medienlöschung ist das eine direkte Datenintegritätslüge gegenüber Aufrufern.

Sicherheit & Performance

  • Rate Limit pro Instanz

    In-Memory-Limits in serverlosen Instanzen skalieren nicht horizontal. Wer mehr Instanzen bekommt, bekommt mehr erlaubte Requests. Das ist Mathematik, keine Schutzmaßnahme.

  • Unbegrenzte Firestore-Aggregationen

    Aggregationspfade ohne harte Obergrenze wachsen mit dem Datenbestand. Spätestens bei echten Mandanten wird aus einer Anfrage ein Kosten- und Latenzexperiment.

  • Secret-/SQL-Scan: kein unmittelbarer Volltreffer

    Statische Prüfung fand keine produktiv wirksamen hardcodierten Secrets und keine offensichtlichen SQL-Injection-Pfade. Das ist der Mindeststandard, kein Entlastungszeugnis.

05 / Vor Staging

Refactoring-Fahrplan

Reihenfolge nach Risiko. Keine kosmetischen Umbenennungen, keine neue Feature-Arbeit, keine weiteren „temporären“ Bypässe. Erst die Grenzen reparieren, dann kann man über Komfort reden.

Direkte Firestore-Zugriffe schließen

Öffentliche Collections deny-by-default. Öffentliche, minimal projizierte Daten ausschließlich über den kontrollierten Gateway oder dedizierte Public-Read-Modelle bereitstellen.

P0 · Stop Ship

Branding-Import gegen SSRF absichern

URL-Parser, DNS-Auflösung gegen private/reservierte Netze, Redirects deaktivieren, Größen-/Timeout-Limits und CSS auf eine explizite Allowlist beziehungsweise Same-Origin beschränken.

P1 · Security

CI zu einer echten Qualitätsgrenze machen

TypeScript-Check, Vitest, Playwright und Lint verpflichtend und blockierend ausführen. Ein fehlender Testschritt ist keine bewusst akzeptierte Teststrategie.

P1 · Delivery

Eine Backend-Implementierung bestimmen

Produktions-Gateway und lokale Services nicht kopieren. Lokale Entwicklung muss den Produktionsvertrag nutzen; Implementierungsduplikate sind zu entfernen, nicht zu synchronisieren.

P1 · Architektur

Jobs deklarativ und revisionssicher deployen

Deployment mit Image-Digest, Runtime-Konfiguration, Service-Account und Rollback-Pfad im Repository beschreiben. Ein gebautes Image ist noch kein Betriebskonzept.

P1 · Betrieb

Alle Systemgrenzen strikt validieren

Zod-Schemas für Settings und jeden öffentlichen Request. Danach Route-Reihenfolge mit Vertragstest absichern; „funktioniert bei meinem Pfad“ ist kein API-Vertrag.

P2 · Verträge

Speicher- und Auslieferungsmodell bereinigen

Öffentliche und private Objekte trennen; signierte URLs nur dort einsetzen, wo sie tatsächlich die Grenze sind. CSP, Swagger-Assets und Fonts auf eine kohärente Strategie reduzieren.

P2 · Hardening

Observability und Fehlersemantik nachziehen

Zentrale Redaction, strukturierte Fehlerklassen, verteiltes Rate Limiting und begrenzte Abfragen. Bei Teilfehlern ehrlich fehlschlagen oder kompensieren; keinesfalls Erfolg vortäuschen.

P2 · Resilienz

Prüfumfang

Statische Analyse von 505 aktiven Quellmodulen, Konfiguration, Sicherheitsregeln, CI/CD-Manifesten und Deployment-Dateien. Untersucht wurden Architektur, Zugriffspfade, Typ-/Fehlerbehandlung, Sicherheitsgrenzen und naheliegende Skalierungsrisiken.

Explizite Grenze

Kein Code wurde ausgeführt, keine Tests gestartet und keine Dateien im geprüften Repository geändert. Abhängigkeiten wurden ausschließlich aus Manifests bewertet; ein CVE-Scan war nicht Teil der statischen Prüfung.