fragJulia Launch-Day Audit — 2026-04-04
Pre-launch functionality audit identifying 1 critical, 2 high, 4 medium, and 3 low-priority issues — including the proxy.ts vs middleware.ts bug, Stripe origin handling, and CSP gaps.
Status note 2026-04-25: This audit was performed 2026-04-04. Many findings may already be resolved. Spot-checked findings have inline annotations below; readers should treat unannotated findings as "state unverified since audit date" — not as "still current". For an authoritative picture, run a fresh audit.
Summary
Full functionality audit ahead of launch. 10 issues identified:
- 1 CRITICAL (launch blocker)
- 2 HIGH (should fix before launch)
- 4 MEDIUM (fix soon after launch)
- 3 LOW (non-urgent improvements)
CRITICAL
1. Middleware not active — route protection, session refresh, and CSP headers are dead code
Files: apps/web/proxy.ts, apps/web/lib/supabase/middleware.ts
Next.js requires middleware to live in a file named middleware.ts at the project root (apps/web/middleware.ts). The project has all the middleware logic in apps/web/proxy.ts — a file Next.js will never execute.
Impact:
- Protected routes (
/profil,/dokumente,/inbox,/admin,/community/neu,/breastfriend/chat) are accessible without authentication. Any visitor can navigate directly to these URLs. Client-sideuseAuth()hooks will eventually redirect, but the full page JS loads first, flashing UI and leaking component structure. - Admin routes (
/admin/*) have no server-side admin check. The middleware'sis_adminprofile query never runs. Admin pages rely solely on client-side checks. - Supabase session refresh never runs. The
updateSession()call in the middleware refreshes auth tokens on every request. Without it, users with expired tokens will silently lose their session, leading to confusing 401 errors mid-session. - CSP headers from middleware are not applied. The Content-Security-Policy header (frame-ancestors, connect-src, base-uri, etc.) defined in the middleware never reaches the browser. Only the basic security headers from
next.config.mjsare active.
Fix: Rename apps/web/proxy.ts to apps/web/middleware.ts and export the proxy function as middleware:
// apps/web/middleware.ts
export { proxy as middleware, config } from './proxy'Or simply rename proxy.ts to middleware.ts and rename proxy() to middleware().
Update 2026-04-25: Resolved.
apps/web/proxy.tsno longer exists;apps/web/middleware.tsis in place and re-exportsupdateSessionfrom@/lib/supabase/middleware.
HIGH
2. Document simplify API uses wrong column name — file_path vs storage_path
File: apps/web/app/api/documents/simplify/route.ts:90
.download(doc.file_path); // BUG: column is `storage_path`, not `file_path`The user_documents table uses storage_path (confirmed by upload route at line 125 and all other document routes). The simplify route queries select("*") which returns the raw DB columns, so doc.file_path is undefined.
Impact: When a user uploads a text document and tries to simplify it via document ID, the Supabase storage download silently fails (undefined path), and the endpoint falls through to the "no content" error. The feature only works when rawText is provided directly (the Arztbrief page's text input), not when selecting an uploaded document.
Fix: Change doc.file_path to doc.storage_path on line 90.
Update 2026-04-25: Resolved by architecture change. The current
apps/web/app/api/documents/simplify/route.tsno longer downloads files at all — it readsocr_confirmed_textfrom theuser_documentsrow (requiresocr_status === "confirmed") or acceptsconfirmedTextdirectly. Neitherdoc.file_pathnordoc.storage_pathis read by this route anymore.
3. CSP connect-src missing LiveKit domain — voice connections would be blocked
File: apps/web/lib/supabase/middleware.ts:126
The Content-Security-Policy connect-src directive lists:
'self' https://*.supabase.co wss://*.supabase.co https://vercel.live https://api.stripe.comMissing: LiveKit Cloud WebSocket/HTTPS connections (e.g., wss://*.livekit.cloud, https://*.livekit.cloud). When the middleware is activated (issue #1), voice token requests and WebRTC signaling will be blocked by CSP.
Impact: Voice feature will not work once CSP is properly enforced.
Fix: Add LiveKit domains to connect-src:
connect-src 'self' https://*.supabase.co wss://*.supabase.co https://vercel.live https://api.stripe.com wss://*.livekit.cloud https://*.livekit.cloudMEDIUM
4. Voice feature has API endpoint but no frontend UI
Files: apps/web/app/api/voice/token/route.ts (exists), no page/component uses it
The /api/voice/token endpoint generates LiveKit tokens and is fully implemented, but there is no frontend page, component, or UI element that calls this endpoint. No <audio> elements, no LiveKit client SDK usage in any component, no /voice or /chat voice toggle.
Impact: Voice feature is invisible to users. The backend is ready but there's nothing to interact with.
Action: Either build the voice UI before launch or remove the endpoint to reduce attack surface. If shipping later, document it as a post-launch feature.
5. Stripe checkout uses unvalidated origin header for redirect URLs
File: apps/web/app/api/stripe/checkout/route.ts:56
const origin = request.headers.get("origin") || process.env.NEXT_PUBLIC_SITE_URL || "";The origin header is user-controllable. While browsers enforce it in standard requests, server-to-server or modified requests could spoof it, causing success_url and cancel_url to redirect to an attacker-controlled domain after checkout.
Impact: Potential open redirect after Stripe checkout. Low exploitability (Stripe validates redirect domains in dashboard settings), but defense-in-depth warrants a fix.
Fix: Validate origin against an allowlist or always use process.env.NEXT_PUBLIC_SITE_URL:
const origin = process.env.NEXT_PUBLIC_SITE_URL || request.headers.get("origin") || "";6. Missing global error boundary (error.tsx)
File: apps/web/app/error.tsx — does not exist
The app has not-found.tsx (404) but no error.tsx for runtime errors. Unhandled exceptions render the default Next.js error page, which is unbranded and unhelpful.
Impact: Users hitting a runtime error see a generic white page with no navigation or recovery path. Bad UX on launch day if any edge case triggers an error.
Fix: Create apps/web/app/error.tsx with a branded error page that includes navigation back to home.
Update 2026-04-25: Resolved.
apps/web/app/error.tsxexists on main with a brandedGlobalErrorcomponent (Geborgenheit illustration, retry button, link back to/).
7. Password reset uses NEXT_PUBLIC_DEV_SUPABASE_REDIRECT_URL env var
File: apps/web/app/passwort-vergessen/page.tsx:30
redirectTo: process.env.NEXT_PUBLIC_DEV_SUPABASE_REDIRECT_URL ||
`${window.location.origin}/auth/callback?next=/passwort-neu`,The env var has a DEV_ prefix suggesting it's for development only. If set in production, it overrides the production redirect URL. If unset, falls back to window.location.origin which is correct but fragile.
Impact: If someone accidentally sets this env var in production, password reset emails will redirect to the dev URL. The NEXT_PUBLIC_ prefix means it's exposed in the client bundle.
Fix: Remove the dev env var reference. Use only the production-safe fallback:
redirectTo: `${window.location.origin}/auth/callback?next=/passwort-neu`,LOW
8. Debug console.log left in production page
File: apps/web/app/community/live/yoga/new/page.tsx:157
console.log("Creating meet:", formData)This logs user form data to the browser console. Minor information leak.
Fix: Remove the line.
Update 2026-04-25: Resolved. The current
handleSubmitinapps/web/app/community/live/yoga/new/page.tsxno longer contains aconsole.log("Creating meet:", formData)line; it just sets submitting state, awaits a 1.5s placeholder, and routes to/community/live/yoga?created=true.
9. Routes /zeitstrahl, /arztbrief, /forschung missing from middleware protectedPrefixes
File: apps/web/lib/supabase/middleware.ts:69-78
These pages require authentication (they use useAuth() client-side) but are not listed in the middleware's protectedPrefixes. Users see a brief flash of the page before the client-side redirect kicks in.
Currently protected paths:
const protectedPrefixes = [
'/dokumente', '/inbox', '/profil', '/community/neu',
'/community/moderation', '/community/live/yoga/new',
'/community/live/yoga/mine', '/breastfriend/chat',
]Missing: /zeitstrahl, /arztbrief, /forschung, /chat (the main chat page)
Impact: UX issue — brief flash of unauthenticated content before redirect. No security risk since API calls still require auth tokens.
Fix: Add the missing routes to protectedPrefixes.
Update 2026-04-25: Partially superseded —
/forschungis now a permanent 301 redirect todocs.fragjulia.de(per Pillar A migration inapps/web/next.config.mjs), so it no longer needsprotectedPrefixescoverage. The status of/zeitstrahl,/arztbrief, and/chatinprotectedPrefixesis unverified — re-checkapps/web/lib/supabase/middleware.tsif this matters.
10. Curated clinical studies data is static — no live data source
File: apps/web/app/api/studies/route.ts
The /api/studies endpoint returns 5 manually curated studies (last verified 2026-04-03). There is no live connection to DRKS or ClinicalTrials.gov.
Impact: Data will become stale if not manually updated. Studies may close recruitment or new studies may open. Users may miss relevant opportunities.
Action: Acceptable for launch with a "last verified" date shown to users. Plan a process for periodic manual review, or build a ClinicalTrials.gov API integration post-launch.
Additional Observations (Not Issues)
Positive Findings
- Rate limiting is consistently applied across all API routes
- Subscription tier gating is properly implemented and tested
- GDPR/DSGVO compliance: Data export endpoint exists, chat cleanup cron runs daily, document deletion includes storage cleanup
- Input sanitization is solid across API routes
- Newsletter has proper double opt-in with 24hr token expiration and HMAC-verified unsubscribe
- Document sharing uses bcrypt passwords and signed URLs with 5-min expiration
- Security headers in
next.config.mjsprovide baseline protection (X-Frame-Options, X-Content-Type-Options, Referrer-Policy) - SEO is well-configured with sitemap, robots.txt, JSON-LD, and OG tags
- Accessibility baseline is present: skip-to-content, WCAG viewport, aria-labels, semantic HTML
- Tests cover critical paths: subscription tiers, route access, design tokens, curated data integrity
Feature Completeness Checklist
| Feature | Status | Notes |
|---|---|---|
| Auth (login/register/reset) | OK | Supabase Auth, email confirmation |
| Chat (Julia AI) | OK | GPT-4o-mini, streaming, history, export |
| Voice | Backend only | Token endpoint exists, no UI |
| Documents | OK (bug) | Upload/simplify/share/export — simplify has column bug |
| Timeline | OK | Full CRUD for treatment events |
| Community | OK | Posts, moderation, live yoga events |
| Messaging/Inbox | OK | Threads, blocking, read receipts |
| Breastfriend matching | OK | Premium feature, phase-based matching |
| Clinical studies | OK (static) | 5 curated studies, scoring algorithm |
| Kliniken directory | OK | DKG-certified centers, search/filter |
| Newsletter | OK | Double opt-in, HMAC unsubscribe |
| Payments (Stripe) | OK (minor) | Checkout, portal, webhook — origin issue |
| Admin panel | OK (depends on #1) | CMS, moderation, bans, email preview |
| GDPR export | OK | Full data export as JSON |
| Mobile app | Phase 2 | Auth + Chat done, Lexikon + Community placeholder |
Ingested from repo-root
LAUNCH-AUDIT-2026-04-04.mdon 2026-04-25 (#647).
Sprint — #337 · #358 · #328 — Selfhosting · Chat · Voice
Cross-epic coordination doc for 36 open issues across Document Pipeline (#328), Chat Bedrock + Guardrails (#358), and Voice Infrastructure (#337). Identifies file-level conflicts, shared blockers, and recommended execution order.
Handover 2026-04-22 — SSOT consolidation
Context, discoveries, delivered scaffolding, and open work for the SSOT consolidation effort. Written for the next session or contributor to pick up without prior context.