What we were looking at
The founder built this app using Lovable to move fast. The core flow: users create a profile, upload documents, and the app uses AI to match them with relevant opportunities. It integrates with third-party APIs for data scraping, an AI gateway for document parsing and matching, and a transactional email service.
The tech stack is React 18 + Vite, Supabase for auth/database/storage/edge functions, and multiple external APIs. The database has 7 tables with Row Level Security enabled on all of them. Six edge functions handle the backend logic.
On the surface, the app worked. Users could sign up, upload documents, search for opportunities, and track their activity. The question was whether it was safe to put real users on it.
Not everything was broken
RLS on every table
Row Level Security was enabled on all 7 database tables. This one had policies in place from the start.
Auth on sensitive functions
The document parsing, notification, and data-processing edge functions all verified JWT tokens correctly. The pattern was there, just not applied everywhere.
Rate limiting infrastructure
A database-level rate limiting function and table already existed. It just wasn’t wired up to the endpoints that needed it most.
Error handling on integrations
External API calls included timeout handling, 429 (rate limit) responses, and 402 (payment) error codes. The defensive patterns were solid.
The security patterns already existed in some places. The roadmap focused on applying them everywhere. Targeted fixes, not a rebuild.
What would have caused real damage
The edge function that searches for opportunities makes up to 3 third-party API calls per request. It accepts requests without verifying any auth token. Anyone with the project’s public anon key (embedded in the frontend JavaScript) can call it directly. A single script could exhaust the API budget in under an hour.
Request → parse JSON → call third-party API
// No rate limiting, no usage caps, no auth check
Add JWT verification (the pattern already exists in other functions), add per-user rate limiting, and add a global daily call cap.
The AI matching function accepts a user ID from the request body without verification, then uses an elevated service-role client to read that user’s profile data. Any caller can read any user’s personal information by guessing or enumerating user IDs.
const client = createClient(url, SERVICE_ROLE_KEY) // bypasses RLS
// Anyone can read any user's skills, job title, location
The audit also found: .env file committed to git (secrets pattern risk) and wildcard CORS on all edge functions (cross-site request forgery). Both critical, both fixable in under 2 hours combined.
Where it breaks under load
The audit modeled API cost exposure from 10 users to 1,000+ users and found that without auth and rate limiting, a single script could drain the API budget in under an hour. The report included exact cost projections and the specific endpoints responsible.
Beyond security
Plus 18 more findings across performance, code quality, architecture, and scalability. Each with severity, evidence, and a recommended fix with effort estimate.
Production readiness roadmap
Close all critical security vulnerabilities. Auth, rate limiting, CORS, input validation.
Performance, accessibility, and code quality fixes. Database optimization, error handling.
Test coverage, bundle optimization, caching, and dependency cleanup.
Monitoring, full accessibility compliance, end-to-end testing, vendor evaluation.
The audit turned 26 unknown unknowns into a prioritized, actionable roadmap. Every finding includes severity, code evidence, a recommended fix, and an effort estimate.