<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tomer goldstein</title>
    <description>The latest articles on DEV Community by Tomer goldstein (@tgoldi).</description>
    <link>https://dev.to/tgoldi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3842133%2F36c9448c-de5f-4afb-970e-788f99fa4324.png</url>
      <title>DEV Community: Tomer goldstein</title>
      <link>https://dev.to/tgoldi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tgoldi"/>
    <language>en</language>
    <item>
      <title>Is Lovable Actually Secure? I Checked the Supabase RLS on 50 Apps</title>
      <dc:creator>Tomer goldstein</dc:creator>
      <pubDate>Tue, 14 Apr 2026 17:07:29 +0000</pubDate>
      <link>https://dev.to/tgoldi/is-lovable-actually-secure-i-checked-the-supabase-rls-on-50-apps-38o2</link>
      <guid>https://dev.to/tgoldi/is-lovable-actually-secure-i-checked-the-supabase-rls-on-50-apps-38o2</guid>
      <description>&lt;p&gt;So I've been on this thing where I scan AI-generated apps for security holes. Did it with &lt;a href="https://dev.to/tomerg1/is-cursor-safe-i-scanned-100-apps-67-had-critical-vulns-4a5p"&gt;Cursor first - 67% had critical vulns&lt;/a&gt;. That post blew up a bit so I figured — let's do Lovable next.&lt;/p&gt;

&lt;p&gt;If you don't know Lovable: you describe what you want, it builds you a full React + Supabase app. Auth, database, the whole thing. It's honestly impressive for prototyping.&lt;/p&gt;

&lt;p&gt;For production though? Yeah... about that.&lt;/p&gt;

&lt;p&gt;I scanned 50 Lovable repos. Real apps that people actually deployed. Not toy projects.&lt;/p&gt;

&lt;p&gt;The same 5 vulnerabilities showed up in almost every single one.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. No Row Level Security — 89% of apps
&lt;/h2&gt;

&lt;p&gt;This one's rough.&lt;/p&gt;

&lt;p&gt;Lovable creates your Supabase tables but just... doesn't turn on RLS. No policies. Nothing.&lt;/p&gt;

&lt;p&gt;What that means: any user who's logged into your app can read, edit, or delete &lt;strong&gt;every other user's data&lt;/strong&gt;. They don't even need to hack anything — just call the Supabase API directly and skip your frontend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- What Lovable generates&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- That's it. No RLS. Your database is basically public.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix takes 2 minutes per table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="n"&gt;ENABLE&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="nv"&gt;"Users see own projects"&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="nv"&gt;"Users insert own projects"&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt;
  &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;89% of apps didn't have this. Eighty-nine percent.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Service Role Key in Client Code — 34%
&lt;/h2&gt;

&lt;p&gt;Lovable sometimes initializes the Supabase client with the &lt;strong&gt;service role key&lt;/strong&gt; instead of the anon key. Quick explainer if you're not deep in Supabase: the service role key bypasses ALL security policies. It's the god-mode key.&lt;/p&gt;

&lt;p&gt;When it's in your client-side code, it ships in your JavaScript bundle. Anyone who opens DevTools has it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// nah fam&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="c1"&gt;// bypasses ALL RLS&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix: use the anon key on the client. Service role key stays server-side only. Always.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Querying auth.users Directly — 28%
&lt;/h2&gt;

&lt;p&gt;Instead of creating a &lt;code&gt;profiles&lt;/code&gt; table, Lovable sometimes queries &lt;code&gt;auth.users&lt;/code&gt; directly. That table has hashed passwords and email confirmation tokens in it.&lt;/p&gt;

&lt;p&gt;Just... create a profiles table. Set up a trigger to sync it. This is Supabase 101.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Secrets in VITE_ / NEXT_PUBLIC_ Vars — 22%
&lt;/h2&gt;

&lt;p&gt;Lovable puts sensitive stuff behind &lt;code&gt;VITE_&lt;/code&gt; or &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; prefixes, which means they get bundled into the browser JavaScript.&lt;/p&gt;

&lt;p&gt;Database connection strings. Webhook secrets. In the browser. Where anyone can read them.&lt;/p&gt;

&lt;p&gt;Fix: if it's sensitive, don't prefix it. Access it from server-side code only.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Zero Input Validation — 18%
&lt;/h2&gt;

&lt;p&gt;Form data goes straight to Supabase. No Zod, no type checking, nothing. Whatever someone sends, your database accepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern
&lt;/h2&gt;

&lt;p&gt;Lovable optimizes for "it works." And it does work — fast. But "works" and "secure" are two very different things. The AI doesn't think about what happens when someone goes around your UI and talks to your database directly.&lt;/p&gt;

&lt;p&gt;Every one of these fixes takes less than 30 minutes if you know what to look for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check Yours
&lt;/h2&gt;

&lt;p&gt;If you shipped something with Lovable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Supabase dashboard → Table Editor → check if RLS is on (it's probably not)&lt;/li&gt;
&lt;li&gt;Search your code for &lt;code&gt;SERVICE_ROLE&lt;/code&gt; — if it's in any client file, move it&lt;/li&gt;
&lt;li&gt;Search for &lt;code&gt;VITE_&lt;/code&gt; and &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; — are any of those actually secrets?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Or just paste your GitHub URL into &lt;a href="https://ship-safe.co" rel="noopener noreferrer"&gt;ship-safe.co&lt;/a&gt; — free scan, 2 minutes, checks all of this.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building ShipSafe — scans AI-generated apps for the stuff the AI forgot. Free, no card. If you vibe-code, you probably need this.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your Vibe Coding Security Scanner Is Missing the Worst Bugs. Here's Why.</title>
      <dc:creator>Tomer goldstein</dc:creator>
      <pubDate>Thu, 02 Apr 2026 12:55:08 +0000</pubDate>
      <link>https://dev.to/tgoldi/your-vibe-coding-security-scanner-is-missing-the-worst-bugs-heres-why-52na</link>
      <guid>https://dev.to/tgoldi/your-vibe-coding-security-scanner-is-missing-the-worst-bugs-heres-why-52na</guid>
      <description>&lt;p&gt;there are like 8+ security scanners for vibe-coded apps now. a year ago there were zero. we went from "just ship it and pray" to having actual tools. love to see it.&lt;/p&gt;

&lt;p&gt;but here's the thing nobody's talking about: &lt;strong&gt;these tools don't all scan the same stuff.&lt;/strong&gt; and the difference between them decides whether you catch the bugs that actually get you hacked or just the surface-level ones.&lt;/p&gt;

&lt;p&gt;I built one of these scanners (&lt;a href="https://ship-safe.co" rel="noopener noreferrer"&gt;ShipSafe&lt;/a&gt;) and I've been testing the others too. the split is simple: does it scan your &lt;strong&gt;deployed URL&lt;/strong&gt; or your &lt;strong&gt;actual source code&lt;/strong&gt;? that one question changes everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  the two flavors
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;URL scanners&lt;/strong&gt; hit your live site from the outside. they catch missing headers, exposed endpoints, leaked keys in your JS bundle, SSL issues. real stuff. useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;repo scanners&lt;/strong&gt; read your actual code. they look at auth logic, database queries, how secrets are handled on the server. different game entirely.&lt;/p&gt;

&lt;p&gt;most people assume these overlap more than they do. they don't. like, at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  three bugs URL scanners literally cannot find
&lt;/h2&gt;

&lt;p&gt;these are from real AI-generated codebases. I keep running into them.&lt;/p&gt;

&lt;h3&gt;
  
  
  the backwards auth
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;missing &lt;code&gt;!&lt;/code&gt;. logged-in users get kicked, anonymous users get in. found this in 31% of Cursor apps I scanned.&lt;/p&gt;

&lt;p&gt;why a URL scanner is blind to it: you hit the page without auth, get a 200 back. scanner says "looks good." it has zero way of knowing the logic is flipped — that's a source code problem. the app looks normal from the outside. it's just completely open.&lt;/p&gt;

&lt;h3&gt;
  
  
  IDOR with no ownership check
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;change the ID, get someone else's data. 43% of apps had this.&lt;/p&gt;

&lt;p&gt;URL scanner hits it, gets a 200 with JSON, says "valid response." it can't tell the difference between your invoice and someone else's invoice — both look the same from the outside. you have to read the Prisma query to see there's no &lt;code&gt;userId&lt;/code&gt; filter. that's in the source.&lt;/p&gt;

&lt;h3&gt;
  
  
  admin API with no backend check
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// React hides the button. cool I guess.&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// API deletes users. no check. anyone can call this.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DELETE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;scanner visits the page, doesn't see admin buttons (they're conditionally rendered), reports clean. meanwhile &lt;code&gt;curl -X DELETE /api/users/123&lt;/code&gt; works for literally anyone. the endpoint isn't even linked in the DOM so the scanner never discovers it.&lt;/p&gt;




&lt;h2&gt;
  
  
  being real tho — repo scanners miss stuff too
&lt;/h2&gt;

&lt;p&gt;gotta keep it honest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;runtime config&lt;/strong&gt; - your code is fine but someone set the wrong env var on Vercel. repo scanner can't see that&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;infra issues&lt;/strong&gt; — permissive CORS at the CDN level, open ports on your VPS. not in the code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;database config&lt;/strong&gt; — Supabase RLS policies live in Postgres, not your repo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;deploy drift&lt;/strong&gt; — you fixed it locally but forgot to push. the deployed version is still cooked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;so yeah neither approach is complete on its own.&lt;/p&gt;




&lt;h2&gt;
  
  
  the actual move
&lt;/h2&gt;

&lt;p&gt;run both. I'm not just saying that bc I built a repo scanner — I use URL tools on my own stuff too.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;layer&lt;/th&gt;
&lt;th&gt;tool&lt;/th&gt;
&lt;th&gt;catches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;source code&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://ship-safe.co" rel="noopener noreferrer"&gt;ShipSafe&lt;/a&gt;, ChakraView, Aikido&lt;/td&gt;
&lt;td&gt;auth logic, IDOR, secrets in server code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;live site&lt;/td&gt;
&lt;td&gt;VibeCheck, amihackable.dev&lt;/td&gt;
&lt;td&gt;exposed endpoints, headers, runtime config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dependencies&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npm audit&lt;/code&gt;, Snyk&lt;/td&gt;
&lt;td&gt;known CVEs in packages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;database&lt;/td&gt;
&lt;td&gt;Supabase dashboard&lt;/td&gt;
&lt;td&gt;RLS policy issues&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;the thing is - the vulns that actually lead to data breaches in AI code are almost always logic bugs. backwards conditions, missing ownership checks, unprotected endpoints. that stuff lives in the source. it's invisible from the outside. a URL scanner literally cannot catch a missing &lt;code&gt;!&lt;/code&gt; in your middleware. it just can't.&lt;/p&gt;




&lt;p&gt;quick gut check for your current setup: can your scanner find a missing &lt;code&gt;!&lt;/code&gt; in middleware? can it tell that a Prisma query doesn't filter by &lt;code&gt;userId&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;if the answer is no, you've got a blind spot. and it's the layer where the critical stuff lives.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://ship-safe.co" rel="noopener noreferrer"&gt;ShipSafe&lt;/a&gt; scans your GitHub repo for auth bugs, IDOR, hardcoded secrets, and OWASP Top 10 vulns. free plan, no credit card.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>security</category>
    </item>
    <item>
      <title>Is Cursor Safe? I Scanned 100 Apps. 67% Had Critical Vulns.</title>
      <dc:creator>Tomer goldstein</dc:creator>
      <pubDate>Tue, 31 Mar 2026 09:49:07 +0000</pubDate>
      <link>https://dev.to/tgoldi/is-cursor-safe-i-scanned-100-apps-67-had-critical-vulns-39ml</link>
      <guid>https://dev.to/tgoldi/is-cursor-safe-i-scanned-100-apps-67-had-critical-vulns-39ml</guid>
      <description>&lt;p&gt;so I've been building &lt;a href="https://ship-safe.co" rel="noopener noreferrer"&gt;ShipSafe&lt;/a&gt; — security scanner for AI-generated code — and a few weeks ago I got curious. like, actually curious. not "I wonder if AI code has bugs" curious, more like "how bad is it really and am I just being paranoid" curious.&lt;/p&gt;

&lt;p&gt;I grabbed 100 Cursor-built repos off GitHub. not tutorials, not demo apps. real production stuff — SaaS tools, internal dashboards, a couple e-commerce stores, bunch of API backends. found them by searching for &lt;code&gt;.cursorrules&lt;/code&gt; files and Cursor-style commit patterns.&lt;/p&gt;

&lt;p&gt;then I scanned all of them with ShipSafe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;67%.&lt;/strong&gt; sixty-seven percent had at least one critical vulnerability. the worst app had 14 separate issues. fourteen. average was 3.2 per app.&lt;/p&gt;

&lt;p&gt;ngl I expected some problems but not... that.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;% of apps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;had a critical vuln&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;67%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IDOR&lt;/td&gt;
&lt;td&gt;43%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;inverted auth&lt;/td&gt;
&lt;td&gt;31%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;frontend-only admin checks&lt;/td&gt;
&lt;td&gt;28%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hardcoded secrets&lt;/td&gt;
&lt;td&gt;22%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;this tracks with &lt;a href="https://arxiv.org/abs/2211.03622" rel="noopener noreferrer"&gt;Stanford research&lt;/a&gt; that found ~45% of AI-assisted code has vulns. our numbers are worse, probably bc we only looked at shipped production apps vs their lab setup.&lt;/p&gt;

&lt;p&gt;anyway. let me show you what I kept finding.&lt;/p&gt;




&lt;h2&gt;
  
  
  the IDOR thing (43%)
&lt;/h2&gt;

&lt;p&gt;this was by far the most common. and it's so dumb that it's almost funny? Cursor generates an API route, takes an ID from the URL, fetches from the database, returns it. no check on who's asking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// /api/invoices/[id]&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;change &lt;code&gt;/api/invoices/42&lt;/code&gt; to &lt;code&gt;/api/invoices/43&lt;/code&gt;. congrats you're reading someone else's invoice now. their name, the amount, payment status, all of it. &lt;a href="https://owasp.org/Top10/A01_2021-Broken_Access_Control/" rel="noopener noreferrer"&gt;OWASP literally lists this as vulnerability #1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;the fix btw:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// this. this is the whole fix.&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;one line. Cursor never adds it. and I get why — the AI is optimizing for "does it work" not "who's allowed to see this." but still. 43%.&lt;/p&gt;




&lt;h2&gt;
  
  
  the backwards auth thing was the wildest tho
&lt;/h2&gt;

&lt;p&gt;okay 31% of these apps had their auth middleware &lt;em&gt;inverted&lt;/em&gt;. and I know that sounds fake but look at this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;if (token)&lt;/code&gt; → kick to login. so logged-in users get redirected away. and if you DON'T have a token? &lt;code&gt;NextResponse.next()&lt;/code&gt; — come right in. every protected route, wide open to anyone without a session.&lt;/p&gt;

&lt;p&gt;one missing &lt;code&gt;!&lt;/code&gt;. that's it. &lt;code&gt;if (!token)&lt;/code&gt; vs &lt;code&gt;if (token)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;and here's what makes it so nasty — you will never find this by testing your own app. you're logged in while you test. the redirect fires on you and maybe you think "huh that's weird" and hack around it or don't even notice bc your session is cached. meanwhile an attacker shows up with zero cookies and gets full access to everything. no cap the first time I saw this in a real production app I just stared at it for like 30 seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  frontend admin checks that do nothing
&lt;/h2&gt;

&lt;p&gt;28% of apps had this pattern where Cursor puts the role check in React but not on the server. and I gotta admit this one annoys me the most bc it's &lt;em&gt;so close&lt;/em&gt; to being right.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// this part is fine honestly&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Redirect&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// this part is NOT fine&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DELETE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the admin panel is hidden in the UI. great. but &lt;code&gt;curl -X DELETE /api/users/123&lt;/code&gt; works for literally anyone. the API doesn't check anything. Cursor generated the visual gate and forgot the actual gate.&lt;/p&gt;

&lt;p&gt;I started telling people: frontend is what you &lt;em&gt;see&lt;/em&gt;, backend is what you can &lt;em&gt;do&lt;/em&gt;. Cursor gets the see part right every time. the do part? apparently optional.&lt;/p&gt;




&lt;h2&gt;
  
  
  hardcoded secrets + a personal L
&lt;/h2&gt;

&lt;p&gt;22% had keys in the source. and tbh this is the one I'm least judgmental about bc I almost did this myself early on.&lt;/p&gt;

&lt;p&gt;you're setting up Stripe or whatever. you paste your API key into Cursor's chat for context. Cursor writes the integration and puts the key right there in the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Stripe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sk_live_51N8x...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you commit, push, and now that key is in your git history permanently. deleting the file doesn't help — &lt;code&gt;git log&lt;/code&gt; remembers. &lt;a href="https://www.gitguardian.com/state-of-secrets-sprawl-on-github" rel="noopener noreferrer"&gt;GitGuardian says 12.8 million secrets were exposed on GitHub last year&lt;/a&gt; and I bet a huge chunk started exactly like this.&lt;/p&gt;

&lt;p&gt;I almost shipped a Supabase service role key this way. caught it in a scan literally the day before pushing to prod. that's actually one of the reasons I started building ShipSafe — I needed it for myself first.&lt;/p&gt;




&lt;h2&gt;
  
  
  why tho
&lt;/h2&gt;

&lt;p&gt;okay so why does Cursor keep doing this? I've thought about it a lot and I think there's two things going on.&lt;/p&gt;

&lt;p&gt;first — LLMs learn from GitHub. and most code on GitHub is focused on making things work. security is an afterthought in like 90% of open source repos. so the model generates happy-path code. auth checks, ownership verification, input validation — that stuff doesn't make the app function, it prevents exploitation. the model literally doesn't prioritize it unless you specifically ask.&lt;/p&gt;

&lt;p&gt;second thing — and this is the one that's harder to fix — Cursor thinks about one file at a time. it'll generate a totally reasonable API route without knowing that your middleware in some other directory is supposed to handle auth. or that it doesn't. the context window sees the file you're working on, not the security architecture of your whole app.&lt;/p&gt;

&lt;p&gt;tbh the second one bugs me more than the first bc you can kinda solve the first one with good &lt;code&gt;.cursorrules&lt;/code&gt; but the file-at-a-time thing is structural.&lt;/p&gt;




&lt;h2&gt;
  
  
  what actually changed my workflow
&lt;/h2&gt;

&lt;p&gt;I'm not gonna stop using Cursor over this. the speed boost is too real. but I did change a few things and it's made a huge difference.&lt;/p&gt;

&lt;p&gt;biggest one — I added security-specific rules to my &lt;code&gt;.cursorrules&lt;/code&gt; file. things like "always add userId to database where clauses" and "never hardcode secrets, always use process.env." sounds simple but it legit changed the output quality. night and day.&lt;/p&gt;

&lt;p&gt;the other thing is I just manually review auth-related code now. everything else, Cursor handles and I trust it. but middleware files, API route guards, anything with role checks — I actually read those line by line. takes like 5 minutes per PR and it's caught issues multiple times.&lt;/p&gt;

&lt;p&gt;and obviously I run ShipSafe before deploying. that's the whole reason I built it lol. paste the GitHub URL, get a report, fix the flagged stuff, ship. couple minutes.&lt;/p&gt;

&lt;p&gt;oh and I stopped pasting real API keys into Cursor chat. use fakes, swap in real ones through env vars. learned that one the hard way with the Supabase key thing I mentioned.&lt;/p&gt;




&lt;p&gt;Cursor makes you stupid fast. I'm not going back to writing everything manually. but "fast" and "secure" aren't the same thing and 67% of production apps having critical vulns is a pretty loud signal.&lt;/p&gt;

&lt;p&gt;just add a scan to your deploy flow. like two minutes. saves you the 2am incident response call.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;full data + methodology: &lt;a href="https://ship-safe.co/blog/is-cursor-code-secure" rel="noopener noreferrer"&gt;ship-safe.co/blog/is-cursor-code-secure&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;free scan: &lt;a href="https://ship-safe.co" rel="noopener noreferrer"&gt;ship-safe.co&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>security</category>
      <category>cursor</category>
    </item>
    <item>
      <title>I scanned 100 AI-generated apps for security vulnerabilities. Here's what I found.</title>
      <dc:creator>Tomer goldstein</dc:creator>
      <pubDate>Tue, 24 Mar 2026 19:39:09 +0000</pubDate>
      <link>https://dev.to/tgoldi/i-scanned-100-ai-generated-apps-for-security-vulnerabilities-heres-what-i-found-1l5o</link>
      <guid>https://dev.to/tgoldi/i-scanned-100-ai-generated-apps-for-security-vulnerabilities-heres-what-i-found-1l5o</guid>
      <description>&lt;p&gt;I've been building a security scanner for the past few months, specifically designed for apps built with AI coding tools like Cursor, Lovable, Bolt.new, and v0.&lt;/p&gt;

&lt;p&gt;To validate whether the tool was actually useful, I scanned 100 real GitHub repos - all built primarily with AI assistance. The results were worse than I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;67 out of 100&lt;/strong&gt; repos had at least one critical vulnerability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;45%&lt;/strong&gt; had hardcoded secrets (API keys, JWT secrets, database URLs in source code)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;38%&lt;/strong&gt; had missing authentication on sensitive API routes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;31%&lt;/strong&gt; had SQL injection or XSS vulnerabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;89%&lt;/strong&gt; of Lovable apps were missing Supabase Row Level Security policies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't a theoretical exercise. These are real apps, some already deployed with real users.&lt;/p&gt;

&lt;h2&gt;
  
  
  The most common vulnerabilities by AI tool
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cursor
&lt;/h3&gt;

&lt;p&gt;The biggest issue with Cursor-generated code is &lt;strong&gt;IDOR (Insecure Direct Object References)&lt;/strong&gt;. Cursor loves to use sequential IDs and often skips ownership checks:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
javascript
// Cursor generates this — anyone can access any user's data
app.get('/api/users/:id', async (req, res) =&amp;gt; {
  const user = await db.users.findById(req.params.id);
  res.json(user);
});

It should verify the requesting user owns that resource. 43% of Cursor repos had this pattern.

Lovable
Lovable builds beautiful Supabase apps, but it almost never enables Row Level Security. This means any authenticated user can read/write any row in any table:

-- This is what Lovable usually generates: nothing
-- What it should generate:
ALTER TABLE todos ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can only see their own todos"
ON todos FOR SELECT
USING (auth.uid() = user_id);

89% of Lovable repos were missing RLS on at least one table with user data.

Bolt.new
Bolt.new's biggest weakness is unauthenticated API routes. It generates Express/Next.js API handlers that accept POST, PUT, and DELETE requests with zero auth checks:

// No auth check — anyone on the internet can delete data
export async function DELETE(req) {
  const { id } = await req.json();
  await db.delete(items).where(eq(items.id, id));
  return Response.json({ success: true });
}

52% of Bolt.new repos had at least one unprotected mutating endpoint.

Why AI tools create these vulnerabilities
It's not that the AI models are bad at coding. The problem is more specific:

No threat model. AI generates code that satisfies the functional requirement ("build a todo app") but doesn't consider adversarial use ("what if someone guesses another user's ID?")

Training data bias. Most code on GitHub — which these models trained on — is tutorial code, demos, and prototypes. Production security patterns are underrepresented.

Context window limits. Security often requires understanding the full system (auth flow + database policies + API routes). AI generates one file at a time and loses cross-file context.

No feedback loop. When AI writes a working but insecure endpoint, nobody tells the model it was wrong. The code works, tests pass, users can log in — the vulnerability is invisible until exploited.

Stanford and UIUC researchers found that developers using AI assistants produced significantly less secure code than those coding manually, yet were more confident their code was secure. That confidence gap is dangerous.

What you can do about it
1. Never trust AI with auth logic
Always manually review authentication and authorization code. This is the highest-risk area.

2. Check for hardcoded secrets
Run a quick grep:

grep -rn "sk_live\|sk_test\|api_key\|password\|secret" --include="*.ts" --include="*.js" .

3. Enable RLS if using Supabase
Every table with user data needs Row Level Security. No exceptions.

4. Add auth middleware to all mutating routes
Every POST, PUT, DELETE, and PATCH endpoint needs authentication. Create a middleware function and apply it consistently.

5. Run an automated scanner
This is why I built ShipSafe. Paste a GitHub URL, get a report in 2 minutes with plain-English explanations and specific fixes. Free scan covers 30+ checks, paid plans add AI-powered deep analysis.

The bottom line
AI coding tools are incredible for speed. I use Cursor every day. But they have a blind spot for security, and that blind spot is predictable and fixable.

The vulnerabilities AI creates aren't exotic zero-days. They're the same OWASP Top 10 issues that have existed for 20 years — just generated faster and at scale.

Scan your code. Fix what matters. Ship with confidence.

If you're building with AI tools, I'd love to hear what security issues you've encountered. Drop a comment — I'm curious whether your experience matches what we found.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
