Consumer marketplace / early stage (pre-launch)

From Vibe-Built to Secure: Full Security Audit of an AI-Generated Marketplace App

From Vibe-Built to Secure: Full Security Audit of an AI-Generated Marketplace App

A founder building a peer-to-peer fashion marketplace on Base44 came in ahead of launch wanting to know whether the app was actually secure before putting real buyers, real sellers, and real payments on it. The audit produced 23 findings, including a checkout flow that let buyers purchase items at any price they chose and six database tables with no access controls whatsoever.

A founder building a peer-to-peer fashion marketplace on Base44 came in ahead of launch wanting to know whether the app was actually secure before putting real buyers, real sellers, and real payments on it. The audit produced 23 findings, including a checkout flow that let buyers purchase items at any price they chose and six database tables with no access controls whatsoever.

Stack

Base44, Deno/TypeScript, Stripe Connect, JWT, Row-Level Security, Cloudflare CDN

Primary finding

Unauthenticated Stripe checkout with client-supplied pricing, allowing any buyer to purchase any item at an arbitrary price, alongside missing RLS across six entity tables confirming full cross-account read and write access

Outcome

23 findings remediated or documented across four severity levels. securityheaders.com grade improved from F to A. RLS enabled and verified across all six tables. Price tampering confirmed blocked via live tamper test. Checkout authentication enforced and confirmed. Live API key rotated same day as discovery.

This one came in ahead of launch. A founder building a peer-to-peer fashion marketplace on Base44 reached out because she wanted to know whether the app was actually secure before putting real buyers, real sellers, and real payments on it. The app had been built almost entirely with AI assistance and was close to going live.

That said, this is a pattern I’m seeing more and more. Non-technical founders using AI code builders to ship fast, then realizing somewhere along the way that security wasn’t really part of the process. The app looked polished. Underneath, it had gaps that would have caused serious damage in production.


What the engagement covered

Full penetration test of the live application, manual code review of backend functions, Base44 platform scanner analysis, Row-Level Security configuration across all database tables, Stripe payment-flow testing, session and token security, HTTP security headers, remediation, and direct verification of every fix.


What we found

The audit produced 23 findings across four severity levels. The most serious: unauthenticated Stripe checkout, missing RLS across six tables, checkout price tampering, exposed seller Stripe account IDs, a live API key sitting in platform documentation, and a client-side order creation bypass.

The checkout issue was particularly severe. The backend was building Stripe line items directly from client-supplied price values. Sending a one-cent price for a $69 item generated a valid Stripe checkout session at one cent. Any buyer could have purchased any item for an arbitrary price of their choosing.

The RLS issue was confirmed with two test accounts. Listings, cart items, saved listings, skipped listings, orders, and reviews were all readable and writable by any authenticated user regardless of ownership. Cross-account deletion of another user’s listing returned HTTP 200 before remediation.

The API key finding came up incidentally while investigating Base44 session configuration options. It was reported immediately, before the formal report was delivered, because confirmed high-impact findings like that shouldn’t wait for a report cycle.


What we fixed

Every finding was either remediated through code and configuration changes, or documented as a Base44 platform limitation with specific recommendations for the client to raise directly with the platform. Fixes were verified through direct exploit testing, not just code review.

Authentication was added to checkout and confirmed with unauthenticated requests returning 401. RLS was configured across all six tables and retested with cross-account read and delete attempts. Checkout was rewritten to fetch listing prices server-side, ignoring client-supplied values entirely. A tamper test using a one-cent price for a $69 item returned a Stripe session at the real $69 price after remediation.

The User table was locked down to protect seller Stripe account IDs. X-Frame-Options and Permissions-Policy were enabled, moving the app from an F to an A on securityheaders.com. The Stripe open redirect was fixed by replacing the client-controlled Origin header with a hardcoded application URL. The API key was rotated by the client the following day after being reported urgently.


Results

securityheaders.com grade before remediation: F. After: A. Tables with RLS configured moved from 0 of 6 to 6 of 6. Checkout authentication moved from unenforced to enforced. Price tampering moved from exploitable to blocked. Cross-account data access moved from confirmed exploitable to confirmed blocked.


A note for founders building on AI platforms

Apps built with AI assistance can reach production quality fast, that part is genuinely impressive. Security does not follow automatically, and this engagement is a fairly clear illustration of why.

The app’s surface area was defined by the platform. The security posture was defined by what the developer configured. Base44 provides the infrastructure for RLS, but it has to be turned on. Authentication has to be added to functions explicitly. Stripe prices have to be fetched server-side by choice.

None of these are things an AI code builder will reliably flag unprompted. They require someone who understands what an attacker would try, not just what the app is supposed to do.


Related reading: what actually happens when Supabase RLS is misconfigured and the 30-minute security checklist for AI-built apps.

Browse other audits: the B2B API exposure audit and the developer tooling logout audit.

Back to case studies

(function() { function applyMainRole() { var hero = document.getElementById('hero'); if (!hero) return false; var node = hero; while (node.parentElement && node.parentElement !== document.body) { node = node.parentElement; } if (node && node.parentElement === document.body) { node.setAttribute('role', 'main'); return true; } return false; } if (applyMainRole()) return; var attempts = 0; var interval = setInterval(function() { attempts++; if (applyMainRole() || attempts > 20) { clearInterval(interval); } }, 250); })();