Security Audit Report
NotElon VibeCheck Pro Audit
Executive Summary
TaskFlow Pro has 2 critical vulnerabilities that require immediate attention. The Supabase service role key is exposed in client-side code, granting full database access to anyone who inspects the browser. Three database tables lack Row Level Security, meaning any authenticated user can read all records.
Additionally, 3 high-severity issues were found: no rate limiting on authentication (enables brute-force attacks), CORS misconfiguration (allows cross-origin attacks), and missing Content Security Policy (enables XSS).
The good news: every issue found has a clear fix. AI fix prompts are provided below that you can paste directly into Cursor or your preferred AI coding tool. Estimated fix time: 2-4 hours for all issues.
Detailed Findings
Hardcoded Supabase Service Role Key in Client Code
📁 src/lib/supabase.ts:14
The Supabase service role key is hardcoded directly in client-side code. This key bypasses Row Level Security (RLS) and grants full read/write access to your entire database. Anyone can extract it from your browser's JavaScript bundle.
An attacker can read, modify, or delete ALL data in your database, including other users' records. Full database takeover.
Fix the Supabase configuration in src/lib/supabase.ts. Remove the hardcoded service role key from client code. Use only the anon key on the client side. Move any service role key usage to a server-side API route or Edge Function. The anon key should be in NEXT_PUBLIC_SUPABASE_ANON_KEY and the service role key should ONLY exist in SUPABASE_SERVICE_ROLE_KEY (no NEXT_PUBLIC_ prefix).
Missing Row Level Security (RLS) on 3 Tables
📁 Database: profiles, messages, payments
Three database tables have RLS disabled. Any authenticated user can query all rows in these tables using the Supabase client, regardless of ownership.
Users can read other users' profiles, private messages, and payment records. GDPR/privacy violation. Potential for data exfiltration.
Enable Row Level Security on the profiles, messages, and payments tables in Supabase. Add policies that restrict SELECT, INSERT, UPDATE, and DELETE to only the authenticated user's own rows. For profiles: auth.uid() = id. For messages: auth.uid() = sender_id OR auth.uid() = recipient_id. For payments: auth.uid() = user_id. Test by logging in as User A and confirming you cannot see User B's data.
No Rate Limiting on Authentication Endpoints
📁 app/api/auth/login/route.ts
The login endpoint accepts unlimited requests with no rate limiting. An attacker can brute-force passwords or perform credential stuffing attacks at scale.
Account takeover via brute force. If users reuse passwords (most do), compromised credentials from other breaches can be tested against your app at thousands of requests per second.
Add rate limiting to the authentication endpoint at app/api/auth/login/route.ts. Implement a sliding window rate limit of 5 attempts per IP address per 15 minutes. Use Vercel's @vercel/edge-config or upstash/ratelimit package. Return a 429 Too Many Requests response with a Retry-After header when the limit is exceeded. Also add rate limiting to the signup and password reset endpoints.
CORS Wildcard Allows Any Origin
📁 next.config.js:8
Access-Control-Allow-Origin is set to "*" which allows any website to make authenticated requests to your API. Combined with credentials, this enables cross-origin attacks.
A malicious website can make API calls on behalf of your logged-in users, reading their data or performing actions as them.
Update the CORS configuration in next.config.js. Replace the wildcard "*" with your specific domain(s): "https://yourapp.com" and "https://www.yourapp.com". If you have a staging environment, add that too. Remove Access-Control-Allow-Credentials if you keep the wildcard, or lock down the origin. In the API routes, validate the Origin header against an allowlist.
Missing Content Security Policy Header
📁 Deployment configuration
No Content-Security-Policy header is set. This means the browser applies no restrictions on what scripts, styles, or resources can load on your pages.
Cross-site scripting (XSS) attacks can inject and execute arbitrary JavaScript. An attacker could steal session tokens, redirect users, or modify page content.
Add a Content-Security-Policy header to your Next.js configuration. In next.config.js, add headers() that return a CSP with: default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.supabase.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://*.supabase.co. Adjust the domains based on your actual third-party services. Start with report-only mode (Content-Security-Policy-Report-Only) to catch issues before enforcing.
Session Cookies Missing Secure Flags
📁 Browser cookies inspection
Authentication cookies are set without the Secure, HttpOnly, and SameSite=Strict flags. This makes them accessible to JavaScript and transmittable over unencrypted connections.
Session tokens can be stolen via XSS attacks (no HttpOnly) or intercepted on insecure networks (no Secure flag). SameSite=None allows CSRF attacks.
Update your cookie configuration to set all authentication cookies with: Secure (only sent over HTTPS), HttpOnly (not accessible via JavaScript), SameSite=Strict (prevents cross-site request forgery). If using Supabase Auth, these are handled by the SDK but verify your custom cookies match. Check any middleware or API routes that set cookies manually.
Exposed .env.example Contains Real Values
📁 .env.example:3-7
The .env.example file committed to the repository contains what appear to be real API keys and database URLs rather than placeholder values.
If these are real credentials (even for development), they grant access to associated services. Bots continuously scan GitHub for exposed credentials.
Replace all real values in .env.example with clearly fake placeholders: SUPABASE_URL=https://your-project.supabase.co, SUPABASE_ANON_KEY=your-anon-key-here, DATABASE_URL=postgresql://user:password@localhost:5432/mydb. Run "git log --all --full-history -- .env.example" to check if real values were ever committed. If so, rotate ALL those credentials immediately -- they are compromised.
No Input Validation on User-Facing Forms
📁 app/dashboard/settings/page.tsx:45
User profile form accepts any input without server-side validation. The display name, bio, and URL fields are rendered without sanitization.
Stored XSS: an attacker can set their display name to a script tag that executes when other users view their profile. Potential for session theft or phishing.
Add server-side input validation to all user-facing form handlers. Use zod or yup to define schemas: display name (string, max 50 chars, alphanumeric + spaces only), bio (string, max 500 chars, strip HTML tags), URL (valid URL format only). Validate on the server side in your API route, not just the client. Use DOMPurify or similar to sanitize any user content before rendering.
Missing X-Frame-Options Header
📁 Deployment configuration
No X-Frame-Options or frame-ancestors CSP directive is set. Your pages can be embedded in iframes on other websites.
Clickjacking attacks: a malicious site can overlay your app in a transparent iframe and trick users into clicking buttons they can't see (like deleting their account or changing settings).
Add X-Frame-Options: DENY header to your Next.js configuration. In next.config.js headers(), add { key: 'X-Frame-Options', value: 'DENY' }. If you need to allow framing from specific domains (like your own), use SAMEORIGIN instead. Also add frame-ancestors 'self' to your Content-Security-Policy header.
Verbose Error Messages in Production
📁 app/api/*/route.ts (multiple)
API error responses include full stack traces and internal error messages in production. These reveal framework versions, file paths, and database structure.
Information disclosure helps attackers map your application internals. Stack traces reveal Next.js version, database driver, and file structure that inform targeted attacks.
Wrap all API route handlers in a try/catch that returns generic error messages in production. Replace catch blocks that return error.message with: return NextResponse.json({ error: 'Internal server error' }, { status: 500 }). Log the full error server-side using console.error or a logging service, but never send it to the client. Check NODE_ENV to conditionally include details only in development.
All Checks Performed
Secrets & Credentials
Authentication & Authorization
HTTP Security Headers
Input & Data Handling
Deployment & Infrastructure
Dependencies & Supply Chain
Want This For Your App?
Every vibe-coded app has different vulnerabilities. Get a report tailored to your specific codebase, stack, and deployment. Results in 24 hours.