# I scanned 13 popular AI-app starter repos. 4 were clean, and the same bug was in 6 of them.

> Source: <https://dev.to/vibecheckai/i-scanned-13-popular-ai-app-starter-repos-4-were-clean-and-the-same-bug-was-in-6-of-them-4hdm>
> Published: 2026-09-16 20:21:17+00:00

I built a small deterministic scanner for the failure patterns that show up in AI-built apps — the ones that don't stop the demo, but break you in production.

Then I pointed it at a corpus of popular open-source starter templates and AI-app repos, because those are what a lot of "vibe-coded" projects are actually built on. If the failure is in the *default* the framework hands you, it should show up even in well-maintained, heavily-starred repos.

It did.

Measured 2026-09-16 with [vibecheck](https://vibechecksai.github.io/vibecheck/), a deterministic static analyser for the production-failure patterns AI-built apps hit.

`Anil-matcha/awesome-generative-ai-apps` is a `vibecheck.py` (stdlib-only, deterministic, no network, test/fixture paths skipped by default). Run on the tree as published, unmodified.
| rule | repos affected | what it means | 
|---|---|---|
| `route-without-auth` | 6 | An API route with no authentication check — the classic 'I'll add auth later'. | 
| `client-exposed-secret` | 5 | A secret-ish variable shipped to the browser bundle via a public prefix. | 
| `dangerous-html` | 5 | Untrusted content rendered as raw HTML (XSS surface). | 
| `auth-todo` | 1 | A `TODO` /stub standing where an auth check should be. | 
| `cors-wildcard` | 1 | `Access-Control-Allow-Origin: *` on an authenticated surface. | 
| `hardcoded-secret` | 1 | A real credential literal committed in source. | 

Projects are anonymised: the counts are static-analysis output and a pattern is a prompt to look, not proof of exploitability. Naming a specific project on unverified counts would be unfair to its maintainers, so the aggregate and rule tables above carry the finding and the raw data stays available for verification.

| project | stars | findings | critical | high | medium | 
|---|---|---|---|---|---|
| repo A | 26746 | 67 | 36 | 6 | 25 | 
| repo B | 817 | 13 | 10 | 2 | 1 | 
| repo C | 1324 | 7 | 1 | 3 | 3 | 
| repo D | 1560 | 4 | 0 | 4 | 0 | 
| repo E | 3052 | 3 | 0 | 2 | 1 | 
| repo F | 1690 | 3 | 0 | 0 | 3 | 
| repo G | 844 | 2 | 2 | 0 | 0 | 
| repo H | 5110 | 1 | 1 | 0 | 0 | 
| repo I | 3417 | 1 | 0 | 1 | 0 | 
| repo J | 2138 | 0 | 0 | 0 | 0 | 
| repo K | 1679 | 0 | 0 | 0 | 0 | 
| repo L | 838 | 0 | 0 | 0 | 0 | 
| repo M | 29404 | 0 | 0 | 0 | 0 | 

Raw per-repo JSON: `results.jsonl`. Reproduce with `python3 scan_corpus.py`.

If you want to run it on your own project before reading further, it's free and open source: [vibechecksai/vibecheck](https://github.com/vibechecksai/vibecheck). Zero dependencies, stdlib only, runs in about a second.

The thing I keep coming back to: none of these are exotic bugs. They're the boring "I'll fix that later" defaults — an unauthenticated route, a secret with a public prefix, unescaped HTML. The AI writes the 80% that works. The last 20% is the part nobody prompts for.
