Security Overview
Security overview: 15-finding audit, status, what's fixed
Security Overview
This page provides an overview of the application's security posture, detailing findings from a recent security audit, their current status, and remediation efforts.
2026-04-14 Security Audit
A point-in-time security audit, captured in an internal security study document, was conducted on 2026-04-14. The audit identified 15 findings, categorized by severity: 1 Critical, 4 High, 5 Medium, and 5 Low. The audit's scope was "defense in depth," meaning not every finding is independently exploitable, but each contributes to the overall risk profile.
Critical Finding (C1)
C1: Account takeover via auto-confirmed registration + email smart-linking.
The registration endpoint was unconditionally calling adminClient.auth.admin.createUser({ email_confirm: true }), bypassing email verification. This, combined with a smart-linking query that attached pre-existing clients rows to a new user.id based on email match, created a vulnerability. An attacker could register with a victim's email and inherit their client history across all professionals.
Status: Fixed in commits 441caff and b47b4a8. The registration route now uses supabase.auth.signUp() in production, which triggers a verification email. The admin path is restricted to development environments. Smart-linking logic was moved to run only after exchangeCodeForSession has verified the inbox.
High Severity Findings (H1-H4)
- H1: Stored XSS via avatar upload. The avatar upload route trusts the client-supplied
Content-Typeheader and does not validate magic bytes. This allows an attacker to upload an SVG with embedded<script>tags.- Status: Open. Remediation requires sniffing file types via a library like
file-type, whitelistingjpeg,png, andwebp, rejectingSVG, re-encoding throughsharp, and setting theContent-Typefrom the sniffed type.
- Status: Open. Remediation requires sniffing file types via a library like
- H2: Mass assignment on the settings endpoint. The settings endpoint manually enumerates fields (
name,phone,bio,sessionDuration,sessionPrice, etc.) rather than spreading the request body. While this prevents direct modification of fields likeplanortrialEndsAt, the finding highlights the absence of Zod shape validation. For example,sessionPricecould be negative, orsessionDurationcould be a string.- Status: Partially mitigated; Zod validation is open.
- H3: Booking GET endpoint leaks professional PII. The booking handler returns the full
professionalsrow, including sensitive data such asemail,phone,stripeCustomerId, andstripeSubscriptionId. This allows unauthenticated users to scrape professional PII.- Status: Open. Remediation requires a
toPublicProfessionalDTO()helper at the route boundary to filter sensitive fields.
- Status: Open. Remediation requires a
- H4: Unauthenticated booking endpoint enables spam and slot-griefing. The booking POST endpoint is IP-rate-limited (5 requests per 10 minutes) but lacks a per-professional cap or captcha. A proxy farm could fill a professional's slots with
pending_confirmationbookings.- Status: Open. Remediation requires Cloudflare Turnstile integration and a per-slug daily booking cap.
Medium Severity Findings (M1-M5)
- M1:
CRON_SECRETis compared using===instead oftimingSafeEqualin the cron reminders endpoint. - M2: Booking upsert logic matches clients by phone or email, which could create an oracle for determining if a given phone/email is a client of a specific professional.
- M3: The AI chat endpoint has no rate limit.
- M4: Registration rate limit.
- Status: Fixed via
getRegisterLimiter()(5 registrations per hour).
- Status: Fixed via
- M5: A server action trusts
professionalIdfrom a React prop without re-verifying it on the server.
Low Severity Findings (L1-L5)
- L1: Several routes lack Zod validation (e.g., settings, account-delete).
- L2: Rate-limit keys are based on raw
x-forwarded-for, which is safe on Vercel but could break off-platform. - L3: No CSRF tokens are used; the application relies on
SameSite=Laxfrom Supabase. - L4: The messages endpoint does not validate the
typeenum. - L5: The booking GET endpoint leaks the full booked-slot schedule.
Current Status and Remediation Pace
Of the 15 findings, Critical finding C1 and Medium finding M4 have been fixed. All other findings remain open. The expected remediation pace is one day per session dedicated to security work. High severity findings H1-H4 are estimated to be cleared within approximately one month at this cadence.
Broader Security Gaps
Beyond the specific audit findings, the following broader security gaps have been identified:
- No Row-Level Security (RLS) policies in Postgres. All authorization is currently handled at the application level through
professional.userId === user.idchecks scattered across the API routes. Any missed check represents a potential data leak. The security study explicitly flags the implementation of RLS policies as a Phase-2 initiative. - No test suite for the main application. The Continuous Integration (CI) pipeline currently only runs type checking, linting, and build steps. The absence of a comprehensive test suite for the main application increases the risk of regressions and undetected vulnerabilities.