Skip to content
Back to field notes
BuildersJune 6, 20267 min read

AI-generated insecure defaults: the security patterns Cursor and Lovable skip

AI coding tools generate working features fast, but they predictably skip a handful of security defaults. This note catalogs them and maps each to a check you can run on your own public URL.

BuildersInsecure defaultsLaunch checks

The defaults that ship when nobody reviews them

When you build with Cursor, Lovable, or a similar tool, the generated code is optimized to make a feature work. The settings that only matter once the app is public are usually left at whatever value got the demo running. That is where insecure defaults in AI-generated code come from: not a single dramatic flaw, but a predictable set of permissive settings that nobody chose on purpose.

This note is a hub, not a deep dive. It lists the patterns these tools commonly skip, gives the one reason each tends to get generated that way, and points to the check you can run on your own deployed app. Where a pattern needs its own article, it links out instead of re-explaining it here.

The useful split is between what is observable from the outside and what is only possible. Some of these defaults are visible in a browser within seconds. Others can only be confirmed by reading the code or testing logged-in behavior. Both matter, but they need different checks.

The public-surface defaults you can verify yourself

These four show up in the deployed app and you can confirm each one from your own public URL. No special tooling, just a browser and the response headers.

  • Wildcard or open CORS. A response header of Access-Control-Allow-Origin: * lets any origin read API responses in a browser. AI tools generate this because * is the value that makes a cross-origin call stop failing during development, and the prompt rarely says which origins should actually be allowed. Check: open DevTools, hit your own API route, and read the Access-Control-Allow-Origin header in the Network tab. A specific origin or no header is the safe result; * on an authenticated endpoint is the suspicious one.
  • Debug mode and verbose errors shipped to production. Framework debug modes, stack-trace error pages, and debug headers expose file paths, library versions, and sometimes config values. They get generated because debug output is what makes local development legible, and the production flag is an extra step that the build-fast loop skips. Check: trigger an error on your deployed app (a bad route, a malformed request) and read the response. A generic error page is safe; a full stack trace or a X-Debug style header is not. See the deep dive on header and debug defaults in cursor-generated-app-security-defaults.
  • Build and deploy artifacts left public. Source maps, a readable .env, an exposed .git directory, or route manifests can ship alongside the app. These appear because the build output is deployed as-is and the tool does not prune what the public bundle should not include. Check: from a clean browser session, request your own /.env, /.git/config, and look for sourceMappingURL references in your production JavaScript. Source maps have their own write-up in lovable-app-security-risks-before-launch and the source-map note it links to.
  • Missing security headers. No Content-Security-Policy, no framing protection, no nosniff means the browser falls back to its most permissive behavior. Header configuration is a deliberate step that generated code almost never includes by default, since the app works without it. Check: read the response headers on your home page and app routes. The header baseline is covered in detail in the security-headers note; this is a fast win because the fix is configuration, not code.

The data-access default you cannot see from outside

The fifth pattern is the one that does not show up in a header check, and it is often the most consequential: permissive data access. A table, a serializer, or an API endpoint returns more than the caller should be allowed to see.

This is generated for a familiar reason. The model wires up a query that returns the data the feature needs, and the question of "who is allowed to read this row" is a separate decision the prompt usually did not specify. The result can be an endpoint that returns every user's records, or a database table that is readable without the authorization the app assumes is there.

A header scan will not catch this, because the response can look completely normal. Confirming it means reading the access rules or testing what a different logged-in account can pull. This is authorization, not authentication: the user may be correctly signed in and still be served data that belongs to someone else.

If your app uses Supabase, this maps directly to row-level security defaults. A table without RLS enabled is readable through the public anon key. The dedicated walkthrough is in lovable-supabase-rls-defaults.

What the public checks prove, and what they do not

Running the four public-surface checks above gives you a concrete, reproducible answer about your deployed app's exposed surface. That is worth doing before the URL spreads, because these defaults are the easiest for anyone to spot once the link is public.

They do not prove the app is secure. A clean header check says nothing about whether your endpoints enforce authorization, whether a query leaks other users' data, or whether business logic can be abused. Those gaps live in the code and in logged-in behavior, and they need code review or manual testing to confirm. Treat the public surface as the first pass, not the whole review.

Where VibeCodeGuard fits

VibeCodeGuard's Launch Check scans the public surface: open or wildcard CORS headers, debug headers, exposed build and deploy artifacts, missing security headers, and secret-shaped strings in the client bundle. It returns severity and fix guidance for each finding. That covers the four observable defaults in this note.

It is not a penetration test, a code review, or an authorization audit. The deeper gaps, like missing data-access rules, are out of scope for a public scan and need a look at the code. This pillar routes those to the relevant articles rather than pretending a URL scan can settle them.

Run a focused Launch Check on the four observable defaults before you share the URL publicly, then book time to review the data-access rules the scan cannot see.

A passing public-surface scan is a clean exterior, not a guarantee. The data-access and authorization checks still need the code.

> launch check

Scan the public surface before launch.

Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.