TL;DR — three findings
1 · The flaw is overwhelmingly one thing: PII tables left world-open. Of 48 real
access-control holes confirmed across both passes, ~83% were an unscoped USING (true) /
WITH CHECK (true) policy on a profiles / users table — readable
(and usually writable) by anyone with the public anon key. Financial (orders) and HR
(employees) tables showed up too, but rarely in open source.
2 · Most using(true) alarms are NOT bugs — and a scanner can't tell.
In Pass 1 we manually cleared 42 using(true) policies as safe by design (scoped
TO service_role, which bypasses RLS anyway). That's more than 2× the 20 real holes in the
same sweep. A string-matching scanner flags all 62 identically and ranks none. Telling the safe ones
from the lethal ones is the entire job.
3 · The builder is rarely fingerprintable from source — and it doesn't matter. Across the leads we sourced, the AI tool was usually undetectable (everything compiles down to a Next.js/Vite app). Where it was visible — Bolt/StackBlitz, Cursor — the same flaw appeared. This is a builder-agnostic default, not a Lovable problem or a Bolt problem.
What breaks first — by class (48 confirmed holes)
| Vulnerability class | Share | What an attacker gets |
|---|---|---|
| PII exposure (profiles/users/customers) | ~83% | Read/rewrite every user's personal data from the browser |
| Financial data (orders/payments) | ~6% | Read/modify order & payment rows |
| Generic data tables (clients/subscriptions) | ~4% | Read/modify business records |
| Captured contacts (contacts) | ~2% | Harvest every contact-form submission |
| Regulated PII (employees/HR) | ~2% | Read employee records — possible regulatory weight |
| Privilege escalation (role/admin table) | ~2% | Self-promote to admin |
The long tail (priv-esc, financial, regulated) is rarer but higher severity — and exactly the kind a generic scanner buries under hundreds of low-value flags.
The false-alarm tax — the headline most people get wrong
Every using(true)-shaped policy found in Pass 1 (n = 1,136), by manual verdict:
| Outcome of a using(true) policy | Count | Verdict |
|---|---|---|
Scoped TO service_role (or postgres/admin role) | 42 | SAFE BY DESIGN — service_role bypasses RLS; default-deny for everyone else |
| Unscoped, on a sensitive table | 20 | REAL P0/P1 — reaches anon/authenticated |
No actual (true) policy (string-match noise) | 13 | NOT APPLICABLE |
Read it again: in this sweep, a using(true) policy on a data table was
more likely to be safe than dangerous. The 10-second test that separates them: does the policy
carry a TO service_role clause? Yes → almost certainly fine. No TO clause
(or TO public/authenticated) → that's the hole.
By builder (where detectable, 25 sourced leads)
| Signal in source | Count | Note |
|---|---|---|
| Next.js / Vite (no builder fingerprint) | 21 | Most AI builders emit this — tool not identifiable from source |
| Bolt / StackBlitz ("Created with StackBlitz") | 2 | Same flaw |
| Cursor (.cursor/) | 2 | Same flaw |
Honest takeaway: you cannot reliably tell which AI tool wrote the code from the repo, and it doesn't change the finding. The vulnerability is in how access control is reasoned about, not which tool typed it.
Limits (so you can trust the rest)
Open-source sample bias. Public repos skew toward pet projects, portfolios and early MVPs — fintech/health/payments are under-represented here vs. what ships privately. The PII dominance is real; the rarity of regulated data is a sampling artifact, not reassurance.
Static, source-only. Findings are read from migrations, not confirmed against a running database. Each is framed as "confirm exact impact in a full review."
The false-alarm ratio is directional, not a precise population rate: "safe" counts service_role scoping on any table; "real hole" counts unscoped policies on sensitive tables. Different denominators — reported as observed counts, not a clean percentage.
Disclosure. Where a flaw was real, the owner was notified privately (responsible disclosure). Findings here are class-level only — no named verdicts.
Why judgment is the product
A scanner greps using(true) and hands you 200 undifferentiated flags. This
sweep is the opposite: a senior read that says this one is fine, this one breaches you, here's the
file:line and the one-line fix. That judgment — clearing the 42 and catching the 20 — is the
entire difference between coverage and a verdict.
Written, async security & readiness reviews — every finding cited at file:line, every boundary named, responsible disclosure always.