Audit #17

Closed
opened 2026-08-13 11:19:06 +02:00 by admin · 4 comments
Owner

Wir haben jetzt einen weitern Stand erreicht.
Hier wäre es angebracht.

Ein Audit durchzuführen.

Prüfe einmal die Sicherheit
Prüfe die Funktionen
Prüfe die Webseite + Datenbank + API

Falls ich was vergessen haben sollte das gut wäre zu prüfen füge es hinzu und schreibe den Bericht als Kommentar.
Sowie eine TO-DO-Liste als extra Kommentar

Wir haben jetzt einen weitern Stand erreicht. Hier wäre es angebracht. Ein Audit durchzuführen. Prüfe einmal die Sicherheit Prüfe die Funktionen Prüfe die Webseite + Datenbank + API Falls ich was vergessen haben sollte das gut wäre zu prüfen füge es hinzu und schreibe den Bericht als Kommentar. Sowie eine TO-DO-Liste als extra Kommentar
Author
Owner

Audit-Bericht (2026-08-13) — Security, Funktionalität, Website/DB/API

Durchgeführt über drei parallele Prüfungen: automatisierter Security-Code-Review, Funktionalitäts-Review, sowie eine manuelle Live-Prüfung der Infrastruktur (SSH auf den NAS, Docker, Backups, Cron, Live-Site).

Gesamteinschätzung vorab

Solide Basis. Keine CRITICAL-Findings, kein IDOR, keine Injection, keine hartcodierten Secrets, das Ownership-Modell (assertPetOwnership/assertAdmin) wird lückenlos durchgesetzt. Der auffälligste wiederkehrende Punkt: Rate-Limiting ist gebaut, aber nur an 4 von ~20 Routern verdrahtet — das kam unabhängig sowohl aus dem Security- als auch dem Funktionalitäts-Review und ist der Punkt mit dem besten Aufwand/Nutzen-Verhältnis zum Nachziehen.


1. Security

CRITICAL: keine.

HIGH

  • Rate-Limiting fehlt auf den missbrauchsanfälligsten Endpunkten: messages.send, reports.create, reactions.toggle, follows.create, reposts.create. Nur posts.create, comments.create, explore.listPets, explore.getTrending nutzen die vorhandene rateLimited()-Middleware. Besonders reports.create hat zusätzlich kein Unique-Constraint gegen wiederholtes Melden desselben Posts durch dieselbe Pet-Identität — beides zusammen kann die Moderations-Queue fluten. messages.send ohne Limit ermöglicht DM-Spam/Harassment.
  • Supabase-Storage-Bucket pet-avatars ohne serverseitige Mime-Type-/Größen-Restriktion: Die Zod-Validierung in posts.ts/media.ts/stories.ts prüft nur, was der Client behauptet. Die signierte Upload-URL selbst bindet weder Content-Type noch Dateigröße. Da der Bucket öffentlich ausgeliefert wird, ist ein hochgeladenes SVG mit eingebettetem Script bei direktem URL-Aufruf ein klassisches Stored-XSS-Muster. Fix ist ein einmaliges Supabase-Dashboard-Setting (fileSizeLimit, allowedMimeTypes), kein Code.

MEDIUM

  • Mux-Webhook-Signaturprüfung überspringt sich selbst (fail-open), wenn MUX_WEBHOOK_SECRET nicht gesetzt ist — sollte in Produktion hart fehlschlagen statt stillschweigend ungeprüfte Events zu akzeptieren.
  • Rate-Limiting vertraut X-Forwarded-For/cf-connecting-ip — sicher, solange der Origin-Server ausschließlich Cloudflare-Traffic annimmt (siehe Infra-Punkt unten, das ist aktuell NICHT der Fall).
  • Admin-/Cron-Secret-Vergleiche nutzen einfaches !== statt crypto.timingSafeEqual — bei einem langen UUID-Secret praktisch kaum ausnutzbar, aber minimaler Aufwand zum Härten.

LOW

  • Redis ohne Passwort (aktuell nur im Docker-Netzwerk erreichbar, kein Host-Port-Mapping — Defense-in-Depth empfohlen).
  • search.*, follows.list*, blocks.list ohne Rate-Limit (Scraping-Vektor bei Wachstum).
  • ads.recordView ohne Dedupe (verfälscht Werbekunden-Reporting, keine Security-Lücke).

Positiv bestätigt: Ownership-Checks konsequent überall, Health-Daten korrekt owner-only (Ausnahme: tokenisierte Health-Card wie spezifiziert), DMs sauber gegen IDOR abgesichert, Admin-Panel doppelt gegated (Pfad-Secret + Rollenprüfung), lückenloses Audit-Log mit IP+Rolle, GDPR-Export/Löschung sauber owner-scoped, keine Injection-Vektoren, keine Secrets im Repo, npm audit 0 Vulnerabilities, Security-Header korrekt gesetzt, Invite-Code-Einlösung rate-limitiert mit Race-Condition-Schutz.


2. Funktionalität

  • Tests: 136/142 grün, 5 todo (dokumentierte Platzhalter in auth.test.ts für einen nie gebauten Clerk-Webhook-Flow — Owner wird stattdessen korrekt per Lazy-Upsert angelegt, nur der Testkommentar ist veraltet), 1 einmalig geflakter Test (Timeout, bei Isolation/Re-Run zuverlässig grün — kein echter Bug).
  • Bekannter Admin-Panel-Button-Bug (weißer Hintergrund auf dunklem Panel) ist auf 4 weiteren Seiten noch offen: posts/page.tsx, verification/page.tsx, log/page.tsx ("Load more"-Buttons), sowie invites/page.tsx (Halbfix — nur hover:bg-zinc-800 gesetzt, kein Basis-bg-zinc-900, also im Ruhezustand weiterhin weiß). Bisher nur auf der Users-Seite vollständig gefixt.
  • 3 veraltete TODO-Kommentare (PostCard.tsx, PostTypeSheet.tsx ×2) referenzieren längst gebaute Features (Story/Milestone-Formulare sind fertig verdrahtet) — irreführend für neue Entwickler, sollten gelöscht werden.
  • 15 leere catch-Blöcke bei Nebeneffekten (Redis-Fan-out, Hashtag-Indexierung, Mention-Notifications) — bewusstes "darf Post-Erstellung nie blockieren"-Pattern, aber keiner davon loggt zu GlitchTip. Fiele z. B. Redis aus, würde das niemand bemerken, bis sich Nutzer über leere Feeds beschweren.
  • i18n-Lücken: src/app/(app)/invite/page.tsx (komplette Seite) und src/components/health/EmergencyVetSection.tsx sind vollständig hartcodiert Deutsch, kein next-intl — für EN-Nutzer aktuell unbenutzbar.
  • Counter-Konsistenz (Reactions/Comments/Reposts, inkl. Admin-Löschpfade): sauber, keine Lücke gefunden.

3. Website / Infrastruktur / DB / API (live geprüft)

  • Docker: pawfeed + pawfeed-redis laufen gesund, keine Fehler in den Container-Logs der letzten 24h.
  • Backups: täglich um 3 Uhr, 14-Tage-Retention, letzter Lauf heute erfolgreich (leere .last-error-Datei).
  • Cron-Jobs (trim-feeds, anniversaries): laufen zuverlässig täglich, durchgehend HTTP 200 im Log.
  • Disk: 72 % belegt (62 GB frei) — aktuell unkritisch, für die Zukunft im Auge behalten.
  • SSL: gültig, läuft am 19.09.2026 ab.
  • Response-Zeit: ~180ms, gesund.
  • Security-Header: HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy alle korrekt gesetzt — aber HSTS wird doppelt gesendet (App setzt max-age=31536000, Cloudflare legt zusätzlich max-age=63072000; preload obendrauf). Browser honorieren nur den ersten Header, funktional unkritisch, aber unsauber — sollte nur an einer Stelle gesetzt werden.
  • Tokenisierte Routen (Health-Card, GDPR-Export) liefern bei ungültigem Token sauber 404, kein Info-Leak.
  • Zwei konkrete, neue Infra-Findings:
    1. Der PawFeed-Container ist über Port 4563 direkt und unverschlüsselt (HTTP, nicht HTTPS) im LAN erreichbar, nicht nur über Cloudflare/HTTPS. Bypasst damit Cloudflares WAF/DDoS-Schutz und TLS für jeden, der auf das LAN zugreifen kann. Sollte auf 127.0.0.1/Docker-internes Netzwerk statt 0.0.0.0 gebunden werden, sodass nur Nginx Proxy Manager intern zugreifen kann.
    2. Unauthentifizierte /api/trpc/*-Aufrufe liefern ein HTML-307-Redirect zu Clerks Sign-in-Seite statt eines sauberen JSON-401. Grund: Clerks auth.protect() in proxy.ts fängt die Route ab, bevor der eigentliche tRPC-Handler (der korrekt UNAUTHORIZED werfen würde) überhaupt erreicht wird. Für Browser-Navigation unproblematisch, aber ein API-Client, der eine abgelaufene Session hat, bekommt eine HTML-Antwort statt eines parsebaren Fehlers.

Separate TODO-Liste folgt als eigener Kommentar.

## Audit-Bericht (2026-08-13) — Security, Funktionalität, Website/DB/API Durchgeführt über drei parallele Prüfungen: automatisierter Security-Code-Review, Funktionalitäts-Review, sowie eine manuelle Live-Prüfung der Infrastruktur (SSH auf den NAS, Docker, Backups, Cron, Live-Site). ### Gesamteinschätzung vorab Solide Basis. Keine CRITICAL-Findings, kein IDOR, keine Injection, keine hartcodierten Secrets, das Ownership-Modell (`assertPetOwnership`/`assertAdmin`) wird lückenlos durchgesetzt. Der auffälligste wiederkehrende Punkt: **Rate-Limiting ist gebaut, aber nur an 4 von ~20 Routern verdrahtet** — das kam unabhängig sowohl aus dem Security- als auch dem Funktionalitäts-Review und ist der Punkt mit dem besten Aufwand/Nutzen-Verhältnis zum Nachziehen. --- ## 1. Security **CRITICAL:** keine. **HIGH** - **Rate-Limiting fehlt auf den missbrauchsanfälligsten Endpunkten:** `messages.send`, `reports.create`, `reactions.toggle`, `follows.create`, `reposts.create`. Nur `posts.create`, `comments.create`, `explore.listPets`, `explore.getTrending` nutzen die vorhandene `rateLimited()`-Middleware. Besonders `reports.create` hat zusätzlich kein Unique-Constraint gegen wiederholtes Melden desselben Posts durch dieselbe Pet-Identität — beides zusammen kann die Moderations-Queue fluten. `messages.send` ohne Limit ermöglicht DM-Spam/Harassment. - **Supabase-Storage-Bucket `pet-avatars` ohne serverseitige Mime-Type-/Größen-Restriktion:** Die Zod-Validierung in `posts.ts`/`media.ts`/`stories.ts` prüft nur, was der Client *behauptet*. Die signierte Upload-URL selbst bindet weder Content-Type noch Dateigröße. Da der Bucket öffentlich ausgeliefert wird, ist ein hochgeladenes SVG mit eingebettetem Script bei direktem URL-Aufruf ein klassisches Stored-XSS-Muster. Fix ist ein einmaliges Supabase-Dashboard-Setting (`fileSizeLimit`, `allowedMimeTypes`), kein Code. **MEDIUM** - Mux-Webhook-Signaturprüfung überspringt sich selbst (fail-open), wenn `MUX_WEBHOOK_SECRET` nicht gesetzt ist — sollte in Produktion hart fehlschlagen statt stillschweigend ungeprüfte Events zu akzeptieren. - Rate-Limiting vertraut `X-Forwarded-For`/`cf-connecting-ip` — sicher, solange der Origin-Server ausschließlich Cloudflare-Traffic annimmt (siehe Infra-Punkt unten, das ist aktuell NICHT der Fall). - Admin-/Cron-Secret-Vergleiche nutzen einfaches `!==` statt `crypto.timingSafeEqual` — bei einem langen UUID-Secret praktisch kaum ausnutzbar, aber minimaler Aufwand zum Härten. **LOW** - Redis ohne Passwort (aktuell nur im Docker-Netzwerk erreichbar, kein Host-Port-Mapping — Defense-in-Depth empfohlen). - `search.*`, `follows.list*`, `blocks.list` ohne Rate-Limit (Scraping-Vektor bei Wachstum). - `ads.recordView` ohne Dedupe (verfälscht Werbekunden-Reporting, keine Security-Lücke). **Positiv bestätigt:** Ownership-Checks konsequent überall, Health-Daten korrekt owner-only (Ausnahme: tokenisierte Health-Card wie spezifiziert), DMs sauber gegen IDOR abgesichert, Admin-Panel doppelt gegated (Pfad-Secret + Rollenprüfung), lückenloses Audit-Log mit IP+Rolle, GDPR-Export/Löschung sauber owner-scoped, keine Injection-Vektoren, keine Secrets im Repo, `npm audit` 0 Vulnerabilities, Security-Header korrekt gesetzt, Invite-Code-Einlösung rate-limitiert mit Race-Condition-Schutz. --- ## 2. Funktionalität - **Tests:** 136/142 grün, 5 `todo` (dokumentierte Platzhalter in `auth.test.ts` für einen nie gebauten Clerk-Webhook-Flow — Owner wird stattdessen korrekt per Lazy-Upsert angelegt, nur der Testkommentar ist veraltet), 1 einmalig geflakter Test (Timeout, bei Isolation/Re-Run zuverlässig grün — kein echter Bug). - **Bekannter Admin-Panel-Button-Bug (weißer Hintergrund auf dunklem Panel) ist auf 4 weiteren Seiten noch offen:** `posts/page.tsx`, `verification/page.tsx`, `log/page.tsx` ("Load more"-Buttons), sowie `invites/page.tsx` (Halbfix — nur `hover:bg-zinc-800` gesetzt, kein Basis-`bg-zinc-900`, also im Ruhezustand weiterhin weiß). Bisher nur auf der Users-Seite vollständig gefixt. - **3 veraltete TODO-Kommentare** (`PostCard.tsx`, `PostTypeSheet.tsx` ×2) referenzieren längst gebaute Features (Story/Milestone-Formulare sind fertig verdrahtet) — irreführend für neue Entwickler, sollten gelöscht werden. - **15 leere `catch`-Blöcke** bei Nebeneffekten (Redis-Fan-out, Hashtag-Indexierung, Mention-Notifications) — bewusstes "darf Post-Erstellung nie blockieren"-Pattern, aber keiner davon loggt zu GlitchTip. Fiele z. B. Redis aus, würde das niemand bemerken, bis sich Nutzer über leere Feeds beschweren. - **i18n-Lücken:** `src/app/(app)/invite/page.tsx` (komplette Seite) und `src/components/health/EmergencyVetSection.tsx` sind vollständig hartcodiert Deutsch, kein `next-intl` — für EN-Nutzer aktuell unbenutzbar. - **Counter-Konsistenz** (Reactions/Comments/Reposts, inkl. Admin-Löschpfade): sauber, keine Lücke gefunden. --- ## 3. Website / Infrastruktur / DB / API (live geprüft) - **Docker:** `pawfeed` + `pawfeed-redis` laufen gesund, keine Fehler in den Container-Logs der letzten 24h. - **Backups:** täglich um 3 Uhr, 14-Tage-Retention, letzter Lauf heute erfolgreich (leere `.last-error`-Datei). - **Cron-Jobs** (`trim-feeds`, `anniversaries`): laufen zuverlässig täglich, durchgehend HTTP 200 im Log. - **Disk:** 72 % belegt (62 GB frei) — aktuell unkritisch, für die Zukunft im Auge behalten. - **SSL:** gültig, läuft am 19.09.2026 ab. - **Response-Zeit:** ~180ms, gesund. - **Security-Header:** HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy alle korrekt gesetzt — **aber HSTS wird doppelt gesendet** (App setzt `max-age=31536000`, Cloudflare legt zusätzlich `max-age=63072000; preload` obendrauf). Browser honorieren nur den ersten Header, funktional unkritisch, aber unsauber — sollte nur an einer Stelle gesetzt werden. - **Tokenisierte Routen** (Health-Card, GDPR-Export) liefern bei ungültigem Token sauber 404, kein Info-Leak. - **Zwei konkrete, neue Infra-Findings:** 1. **Der PawFeed-Container ist über Port 4563 direkt und unverschlüsselt (HTTP, nicht HTTPS) im LAN erreichbar**, nicht nur über Cloudflare/HTTPS. Bypasst damit Cloudflares WAF/DDoS-Schutz und TLS für jeden, der auf das LAN zugreifen kann. Sollte auf `127.0.0.1`/Docker-internes Netzwerk statt `0.0.0.0` gebunden werden, sodass nur Nginx Proxy Manager intern zugreifen kann. 2. **Unauthentifizierte `/api/trpc/*`-Aufrufe liefern ein HTML-307-Redirect zu Clerks Sign-in-Seite statt eines sauberen JSON-401.** Grund: Clerks `auth.protect()` in `proxy.ts` fängt die Route ab, bevor der eigentliche tRPC-Handler (der korrekt `UNAUTHORIZED` werfen würde) überhaupt erreicht wird. Für Browser-Navigation unproblematisch, aber ein API-Client, der eine abgelaufene Session hat, bekommt eine HTML-Antwort statt eines parsebaren Fehlers. --- *Separate TODO-Liste folgt als eigener Kommentar.*
Author
Owner

TODO-Liste aus dem Audit (priorisiert)

Kurzfristig — geringer Aufwand, hoher Nutzen

  • Rate-Limiting ergänzen auf messages.send, reports.create, reactions.toggle, follows.create, reposts.create (Vorlage: posts.create/comments.create, rateLimited()-Middleware existiert bereits)
  • Unique-Constraint gegen wiederholtes Melden desselben Posts durch dieselbe Pet-Identität in reports.create
  • Supabase-Dashboard: pet-avatars-Bucket mit fileSizeLimit + allowedMimeTypes einschränken (kein Code, nur Setting)
  • Mux-Webhook: in Produktion hart fehlschlagen, wenn MUX_WEBHOOK_SECRET fehlt, statt Signaturprüfung zu überspringen
  • Admin-Panel Outline-Button-Fix (bg-zinc-900 hover:bg-zinc-800) auf 4 weitere Seiten nachziehen: posts/page.tsx, verification/page.tsx, log/page.tsx, invites/page.tsx (dort nur Hover gefixt, Basis-Hintergrund fehlt noch)
  • 3 veralteten TODO-Kommentare löschen (PostCard.tsx:57, PostTypeSheet.tsx:52,55) — referenzieren längst gebaute Features
  • Doppelten HSTS-Header bereinigen (aktuell setzen sowohl die App als auch Cloudflare je einen mit unterschiedlichem max-age)

Mittelfristig

  • 15 leere catch-Blöcke bei Nebeneffekten (Redis-Fan-out, Hashtag-Indexierung, Mention-Notifications) um minimales console.error() ergänzen, damit GlitchTip sie erfasst
  • i18n vervollständigen: src/app/(app)/invite/page.tsx (komplette Seite) und src/components/health/EmergencyVetSection.tsx sind aktuell komplett hartcodiert Deutsch
  • crypto.timingSafeEqual für Admin-/Cron-Secret-Vergleiche statt !==
  • Redis-Passwort setzen (Defense-in-Depth, aktuell nur durch fehlendes Host-Port-Mapping geschützt)
  • Container-Port 4563 auf 127.0.0.1/Docker-internes Netzwerk statt 0.0.0.0 binden — aktuell direkt unverschlüsselt im LAN erreichbar, umgeht Cloudflare
  • /api/trpc/* bei fehlender Auth ein sauberes JSON-401 statt HTML-Redirect liefern lassen (Route-Matcher in proxy.ts anpassen)
  • AUTH-01/AUTH-02-Test-Scaffolds in auth.test.ts (5 todo-Tests) entweder auf das tatsächliche Lazy-Upsert-Pattern umschreiben oder Kommentar korrigieren — beschreiben aktuell eine nie gebaute Webhook-Architektur

Bereits bekannt, bewusst zurückgestellt — nicht neu

  • CSP scharf schalten (Issue #5, wartet bewusst auf genug GlitchTip-Violation-Daten)

Nur zur Kenntnis, kein Handlungsbedarf

  • ads.recordView ohne Dedupe — Business-Reporting-Genauigkeit, keine Sicherheitslücke
  • search.*/follows.list*/blocks.list ohne Rate-Limit — niedriges Risiko bei aktueller Nutzerzahl, für später vormerken
## TODO-Liste aus dem Audit (priorisiert) ### Kurzfristig — geringer Aufwand, hoher Nutzen - [ ] Rate-Limiting ergänzen auf `messages.send`, `reports.create`, `reactions.toggle`, `follows.create`, `reposts.create` (Vorlage: `posts.create`/`comments.create`, `rateLimited()`-Middleware existiert bereits) - [ ] Unique-Constraint gegen wiederholtes Melden desselben Posts durch dieselbe Pet-Identität in `reports.create` - [ ] Supabase-Dashboard: `pet-avatars`-Bucket mit `fileSizeLimit` + `allowedMimeTypes` einschränken (kein Code, nur Setting) - [ ] Mux-Webhook: in Produktion hart fehlschlagen, wenn `MUX_WEBHOOK_SECRET` fehlt, statt Signaturprüfung zu überspringen - [ ] Admin-Panel Outline-Button-Fix (`bg-zinc-900 hover:bg-zinc-800`) auf 4 weitere Seiten nachziehen: `posts/page.tsx`, `verification/page.tsx`, `log/page.tsx`, `invites/page.tsx` (dort nur Hover gefixt, Basis-Hintergrund fehlt noch) - [ ] 3 veralteten TODO-Kommentare löschen (`PostCard.tsx:57`, `PostTypeSheet.tsx:52,55`) — referenzieren längst gebaute Features - [ ] Doppelten HSTS-Header bereinigen (aktuell setzen sowohl die App als auch Cloudflare je einen mit unterschiedlichem `max-age`) ### Mittelfristig - [ ] 15 leere `catch`-Blöcke bei Nebeneffekten (Redis-Fan-out, Hashtag-Indexierung, Mention-Notifications) um minimales `console.error()` ergänzen, damit GlitchTip sie erfasst - [ ] i18n vervollständigen: `src/app/(app)/invite/page.tsx` (komplette Seite) und `src/components/health/EmergencyVetSection.tsx` sind aktuell komplett hartcodiert Deutsch - [ ] `crypto.timingSafeEqual` für Admin-/Cron-Secret-Vergleiche statt `!==` - [ ] Redis-Passwort setzen (Defense-in-Depth, aktuell nur durch fehlendes Host-Port-Mapping geschützt) - [ ] Container-Port 4563 auf `127.0.0.1`/Docker-internes Netzwerk statt `0.0.0.0` binden — aktuell direkt unverschlüsselt im LAN erreichbar, umgeht Cloudflare - [ ] `/api/trpc/*` bei fehlender Auth ein sauberes JSON-401 statt HTML-Redirect liefern lassen (Route-Matcher in `proxy.ts` anpassen) - [ ] `AUTH-01`/`AUTH-02`-Test-Scaffolds in `auth.test.ts` (5 `todo`-Tests) entweder auf das tatsächliche Lazy-Upsert-Pattern umschreiben oder Kommentar korrigieren — beschreiben aktuell eine nie gebaute Webhook-Architektur ### Bereits bekannt, bewusst zurückgestellt — nicht neu - CSP scharf schalten (Issue #5, wartet bewusst auf genug GlitchTip-Violation-Daten) ### Nur zur Kenntnis, kein Handlungsbedarf - `ads.recordView` ohne Dedupe — Business-Reporting-Genauigkeit, keine Sicherheitslücke - `search.*`/`follows.list*`/`blocks.list` ohne Rate-Limit — niedriges Risiko bei aktueller Nutzerzahl, für später vormerken
Author
Owner

Autonom umgesetzt (11 von 12 Punkten, Commits b4a49f8 + ebc5267, deployed + live verifiziert)

Alle Punkte außer der Port-4563-Bindung sind erledigt — die habe ich bewusst zurückgestellt, weil sie mit der extern (GUI) verwalteten Nginx-Proxy-Manager-Konfiguration koordiniert werden müsste und im Fehlerfall die Seite von außen unerreichbar machen könnte.

Erledigt

  • Rate-Limiting auf messages.send, reports.create, reactions.toggle, follows.create, reposts.create
  • Unique-Constraints gegen Report-Spam ((reporterPetId, targetPostId) + (reporterPetId, targetPetId)) — mit explizitem DB-Consent umgesetzt, Prismas eigener KI-Sicherheitsmechanismus hat dafür extra nachgefragt
  • Supabase pet-avatars-Bucket: fileSizeLimit (10 MB) + allowedMimeTypes (jpeg/png/webp) gesetzt — entspricht exakt den bereits im Code etablierten Konstanten
  • Mux-Webhook: fail-hard (500) in Produktion ohne gesetztes Secret statt Signaturprüfung zu überspringen
  • Admin-Panel Outline-Button-Fix auf posts, verification, log, invites nachgezogen
  • 3 veraltete TODO-Kommentare gelöscht
  • Doppelter HSTS-Header behoben (App setzte einen schwächeren zusätzlich zu Cloudflares)
  • crypto-freier Constant-Time-Vergleich für ADMIN_SECRET/CRON_SECRET (bewusst ohne node:crypto, da proxy.ts potenziell im Edge-Runtime läuft)
  • 14 vormals leere catch-Blöcke loggen jetzt zu GlitchTip
  • invite/page.tsx + EmergencyVetSection.tsx vollständig lokalisiert (waren 100 % hartcodiert Deutsch)
  • /api/trpc/* liefert bei fehlender Auth jetzt sauberes JSON-401 statt HTML-Redirect — als Nebeneffekt war das vorher auch für publicProcedure-Aufrufe (z. B. explore.listPets) für ausgeloggte Besucher kaputt, ist jetzt mit gefixt
  • auth.test.ts-Scaffold korrigiert: AUTH-01 ist bereits real getestet (in pet-profile.test.ts), AUTH-02 ehrlich als bekannte, unimplementierte Lücke dokumentiert statt eine nie gebaute Webhook-Architektur zu beschreiben
  • Redis-Passwort gesetzt (--requirepass + REDIS_URL entsprechend angepasst), live verifiziert: ohne Passwort NOAUTH, mit Passwort PONG

Zurückgestellt — braucht Abstimmung

  • Container-Port 4563 auf 127.0.0.1/Docker-internes Netzwerk binden statt 0.0.0.0 — würde die Nginx-Proxy-Manager-Weiterleitung (172.17.0.1:4563) betreffen, dort müsste der Forward-Zielwert ggf. mitgeändert werden. Bei Bedarf bitte kurz Bescheid geben, dann gehe ich das zusammen mit dir an.

tsc/Vitest (137/138, 1 bewusst offener todo)/Docker-Build bei jedem Schritt sauber, Live-Site nach jedem Deploy auf HTTP 200 verifiziert.

## Autonom umgesetzt (11 von 12 Punkten, Commits `b4a49f8` + `ebc5267`, deployed + live verifiziert) Alle Punkte außer der Port-4563-Bindung sind erledigt — die habe ich bewusst zurückgestellt, weil sie mit der extern (GUI) verwalteten Nginx-Proxy-Manager-Konfiguration koordiniert werden müsste und im Fehlerfall die Seite von außen unerreichbar machen könnte. ### Erledigt - [x] Rate-Limiting auf `messages.send`, `reports.create`, `reactions.toggle`, `follows.create`, `reposts.create` - [x] Unique-Constraints gegen Report-Spam (`(reporterPetId, targetPostId)` + `(reporterPetId, targetPetId)`) — mit explizitem DB-Consent umgesetzt, Prismas eigener KI-Sicherheitsmechanismus hat dafür extra nachgefragt - [x] Supabase `pet-avatars`-Bucket: `fileSizeLimit` (10 MB) + `allowedMimeTypes` (jpeg/png/webp) gesetzt — entspricht exakt den bereits im Code etablierten Konstanten - [x] Mux-Webhook: fail-hard (500) in Produktion ohne gesetztes Secret statt Signaturprüfung zu überspringen - [x] Admin-Panel Outline-Button-Fix auf `posts`, `verification`, `log`, `invites` nachgezogen - [x] 3 veraltete TODO-Kommentare gelöscht - [x] Doppelter HSTS-Header behoben (App setzte einen schwächeren zusätzlich zu Cloudflares) - [x] `crypto`-freier Constant-Time-Vergleich für `ADMIN_SECRET`/`CRON_SECRET` (bewusst ohne `node:crypto`, da `proxy.ts` potenziell im Edge-Runtime läuft) - [x] 14 vormals leere `catch`-Blöcke loggen jetzt zu GlitchTip - [x] `invite/page.tsx` + `EmergencyVetSection.tsx` vollständig lokalisiert (waren 100 % hartcodiert Deutsch) - [x] `/api/trpc/*` liefert bei fehlender Auth jetzt sauberes JSON-401 statt HTML-Redirect — als Nebeneffekt war das vorher auch für `publicProcedure`-Aufrufe (z. B. `explore.listPets`) für ausgeloggte Besucher kaputt, ist jetzt mit gefixt - [x] `auth.test.ts`-Scaffold korrigiert: AUTH-01 ist bereits real getestet (in `pet-profile.test.ts`), AUTH-02 ehrlich als bekannte, unimplementierte Lücke dokumentiert statt eine nie gebaute Webhook-Architektur zu beschreiben - [x] Redis-Passwort gesetzt (`--requirepass` + `REDIS_URL` entsprechend angepasst), live verifiziert: ohne Passwort `NOAUTH`, mit Passwort `PONG` ### Zurückgestellt — braucht Abstimmung - [ ] Container-Port 4563 auf `127.0.0.1`/Docker-internes Netzwerk binden statt `0.0.0.0` — würde die Nginx-Proxy-Manager-Weiterleitung (`172.17.0.1:4563`) betreffen, dort müsste der Forward-Zielwert ggf. mitgeändert werden. Bei Bedarf bitte kurz Bescheid geben, dann gehe ich das zusammen mit dir an. tsc/Vitest (137/138, 1 bewusst offener `todo`)/Docker-Build bei jedem Schritt sauber, Live-Site nach jedem Deploy auf HTTP 200 verifiziert.
Author
Owner

Letzter Punkt erledigt: Port-4563-Bindung (Commits 9885fc9 + 2d0e390, deployed)

Zusammen mit dir umgesetzt (brauchte Zugriff auf die Nginx-Proxy-Manager-Oberfläche, die ich nicht selbstständig hätte anfassen sollen):

  1. PawFeed dem nginx_default-Docker-Netzwerk hinzugefügt (gleiches Netzwerk wie NPM), Host-Port-Mapping zunächst noch behalten
  2. Erreichbarkeit von pawfeed:3000 aus dem NPM-Container heraus verifiziert (HTTP 200)
  3. NPM-Proxy-Host für pawfeed.org/www.pawfeed.org/pawfeed.neodk.ipv64.de von http://192.168.1.222:4563 auf http://pawfeed:3000 umgestellt
  4. Live-Site über die echte Domain mehrfach verifiziert (200, ~200-370ms)
  5. Host-Port-Mapping 4563:3000 komplett entfernt, redeployed

Verifiziert: Live-Site läuft weiter normal (https://pawfeed.org → 200, tRPC-API antwortet korrekt), direkter Zugriff auf 192.168.1.222:4563 schlägt jetzt fehl (Connection refused), docker ps zeigt für den pawfeed-Container nur noch den internen Port 3000/tcp, kein Host-Mapping mehr. Keine Downtime während der Umstellung (zweistufiges Vorgehen: erst Netzwerk+Verifikation, dann erst Port-Entfernung).

Damit ist der Container nur noch über Nginx Proxy Manager → Cloudflare erreichbar, nicht mehr direkt unverschlüsselt im LAN.


Alle 12 Audit-Punkte sind damit abgeschlossen. Schließe das Issue.

## Letzter Punkt erledigt: Port-4563-Bindung (Commits `9885fc9` + `2d0e390`, deployed) Zusammen mit dir umgesetzt (brauchte Zugriff auf die Nginx-Proxy-Manager-Oberfläche, die ich nicht selbstständig hätte anfassen sollen): 1. PawFeed dem `nginx_default`-Docker-Netzwerk hinzugefügt (gleiches Netzwerk wie NPM), Host-Port-Mapping zunächst noch behalten 2. Erreichbarkeit von `pawfeed:3000` aus dem NPM-Container heraus verifiziert (HTTP 200) 3. NPM-Proxy-Host für `pawfeed.org`/`www.pawfeed.org`/`pawfeed.neodk.ipv64.de` von `http://192.168.1.222:4563` auf `http://pawfeed:3000` umgestellt 4. Live-Site über die echte Domain mehrfach verifiziert (200, ~200-370ms) 5. Host-Port-Mapping `4563:3000` komplett entfernt, redeployed **Verifiziert:** Live-Site läuft weiter normal (`https://pawfeed.org` → 200, tRPC-API antwortet korrekt), direkter Zugriff auf `192.168.1.222:4563` schlägt jetzt fehl (Connection refused), `docker ps` zeigt für den `pawfeed`-Container nur noch den internen Port `3000/tcp`, kein Host-Mapping mehr. Keine Downtime während der Umstellung (zweistufiges Vorgehen: erst Netzwerk+Verifikation, dann erst Port-Entfernung). Damit ist der Container nur noch über Nginx Proxy Manager → Cloudflare erreichbar, nicht mehr direkt unverschlüsselt im LAN. --- **Alle 12 Audit-Punkte sind damit abgeschlossen.** Schließe das Issue.
admin closed this issue 2026-08-13 13:03:55 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/petfeed#17