Counted from our database · refreshed every 5 minutes

Audit AI in numbers

What the open-source scanner has actually seen on auditai.sh, counted by one SQL query and shown unedited. Our own test repositories are left out. Small numbers stay small here.

As of 27 Oct 2026, 17:00 UTC · counting since 11 Sep 2026

What the scanner has looked at

3public repositories scannedDistinct GitHub repositories. Each one counts once, however often it was scanned.
3scans run3 pasted into auditai.sh and 0 pull-request checks by the GitHub App, one per push.
28entry points analyzedRoute handlers, server actions and dynamic pages found in the latest scan of each repository.
29database queries tracedSupabase, Drizzle and Prisma calls reachable from those entry points: the code the rules actually check.

What the rules found

10open findingsRule matches in the latest scan of each repository, minus the ones the repository marked as intentionally ignored. The scanner marks every one "likely": a rule matched and nothing in the code contradicted it. None is proven until a sandbox proof runs.
1critical severity10% of open findings. One user could read or change another user's data, or the admin key is public.
9high severity90% of open findings. A safety net such as row level security is off or lets too much through.

By rule

RuleFindingsShareReal / false positive / unsurePrecision from votes
supabase.storage-policy-without-owner-checkHas a storage policy that checks only the bucket, so every user it applies to reaches every file in it.330%no votesno decided votes yet
supabase.table-without-rlsQueries a table that has row level security turned off from a user-facing client.330%no votesno decided votes yet
supabase.rls-policy-without-caller-predicateHas an RLS policy that lets rows through without checking the caller.220%no votesno decided votes yet
supabase.mass-assignment-from-request-bodyWrites the request body into a table without an allow-list of columns.110%no votesno decided votes yet
supabase.security-definer-function-without-caller-checkHas a SECURITY DEFINER function, callable through the API, that never checks who is calling.110%no votesno decided votes yet

Share is the rule's part of all open findings. Precision is real ÷ (real + false positive), from votes on report pages; unsure votes are listed but never counted. n is the number of real and false-positive votes behind a figure. Votes on earlier scans of a repository count too, so a rule can have votes and no open findings.

Sandbox proofs

A finding is proven only when we reproduce it. "Prove it" on a report runs the app in an isolated sandbox with two synthetic tenants and tries the attack before and after the fix.

Nobody has requested a proof yet. Until someone does, every finding above stays "likely".

Are the findings real?

Anyone reading a report can mark each finding as real, false positive or unsure. This is the public, self-reported view of precision; it is not checked by us, and people who vote are not a random sample.

No votes yet. The first ones will show up here, next to the rule they are about.

Precision on code we had never seen

On 23 Sep 2026 we scanned 30 public Next.js + Supabase repositories the engine had never run on, none of them in an earlier sample. They were picked mechanically from GitHub code search. The selection rules, how many findings would be labeled and what counts as a real finding were all written down before the scan, so the result could not be tuned after the fact. The scan produced 4,428 findings; the code behind a random 100 of them was then read and each one labeled real, a false positive or unsure, with a sentence of reason that cites the line. A second labeller, who did not see those labels, labeled 30 of them independently and gave the same label to 27 (90%); the figures below are the first labeller's.

22%of what we report is a real holen = 100: 22 real, 78 false positives. We would rather publish that than a number we chose.
18%in the blocking tierHigh and critical findings only, the tier a merge-blocking check would act on (n = 17).
100findings read against the codeDrawn at random from the 4,428 the scan produced; they came from 16 of the 30 repositories.

Every blind sample so far

SampleRepositoriesFindings readPrecisionBlocking tier
#5 · 23 Sep 2026 · scanner 2b735be30100 of 4,42822% n = 10018% n = 17
#4 · 19 Sep 2026 · scanner 093aafc30100 of 2,38630% n = 9632% n = 85
#3 · 14 Sep 2026 · scanner 414ae5e20100 of 63531% n = 9932% n = 92
#2 · 13 Sep 2026 · scanner d25b39320100 of 83946% n = 9549% n = 87
#1 · 13 Sep 2026 · scanner 2ef4af120100 of 41436% n = 10035% n = 92

Samples 1 to 3 took at most 20 findings from one repository out of 20, and three repositories ended up supplying 60 of the 100 findings of the third. From sample 4 on it is at most 10 from one repository out of 30, so no single project moves the figure by more than a tenth. That change was written into the protocol before the fourth sample was picked. Sample 4 came out the same as sample 3 (30% against 31%, p = 0.87): the fixes made after the third sample did not raise precision on code the engine had never seen. Sample 5 came out lower, 22% against 30%, but within noise (p = 0.19): the engine changes made since then, including the split into headline findings and leads, did not raise precision on new code either. Because leads never block, only 17 of its 100 findings are in the blocking tier, so that column says little for sample 5.

By rule, sample #5

RuleFindingsReal / false positivePrecision
supabase.service-role-object-access-without-tenant-scopeReads or writes a row by an id from the request without checking whose row it is (IDOR).4611 / 3524% n = 46
supabase.security-definer-function-without-caller-checkHas a SECURITY DEFINER function, callable through the API, that never checks who is calling.173 / 1418% n = 17
supabase.service-role-query-without-authenticationQueries with the service role in a handler that never checks who is calling.165 / 1131% n = 16
supabase.user-controlled-tenant-scopeTakes the tenant or team id from the request instead of the signed-in session.131 / 128% n = 13
supabase.storage-object-access-without-owner-scopeReads or writes a stored file on a path from the request with the service role, without checking whose file it is.30 / 30% n = 3
supabase.storage-policy-without-owner-checkHas a storage policy that checks only the bucket, so every user it applies to reaches every file in it.11 / 0100% n = 1
supabase.server-trusts-unverified-session11 / 0100% n = 1
supabase.role-check-from-user-metadataDecides roles from user_metadata, which users can edit themselves.10 / 10% n = 1
supabase.rls-policy-without-caller-predicateHas an RLS policy that lets rows through without checking the caller.10 / 10% n = 1
supabase.mass-assignment-from-request-bodyWrites the request body into a table without an allow-list of columns.10 / 10% n = 1

Between the samples: the 64 false positives of the first one named gaps in the engine (credential checks other than Supabase auth, guards in a layout, a schema parse as an allow-list, a row picked by the caller's own identity, a write that Row Level Security refuses anyway). Two rounds of fixes removed 28 of those findings and lost none of the 36 real ones, which put precision at 50% on the same labels (n = 72). That number was not blind, because the fixes were made with those labels in view, so the fixed engine was run on new repositories instead, and every later round of fixes was measured the same way: those are the later samples above.

What these figures are not: they do not say we find everything. Measuring that would take a full manual audit of each repository. They also say nothing about findings we prove: a sandbox reproduction is real by construction, which is the whole point of the proof step. The rules with the lowest precision are where the noise lives, and they are what we are fixing next.

The older sample the engine was tuned against

The blind measurements above are the honest ones. This older count is kept because the engine was tuned against it, which is exactly what makes it worth showing next to a blind figure: on 13 Sep 2026 we scanned 26 public Next.js + Supabase repositories, then the code behind every one of the 183 findings was read and labeled real, false positive or unsure. As in the blind samples above, the reading was done by a model working from the code, not by a person. The figures below cover the 131 of them that scanner build ebf88ea still reports; the rest were false positives it no longer raises, and no finding labeled real was lost. Those fixes were made while looking at these labels, so this is not a blind test.

33%of decided labels are realn = 113 findings labeled real (37) or false positive (76). Unsure labels are left out of the figure.
131findings still reportedFrom 15 of the 26 repositories; the others had none after the fixes (183 labeled in total).
14%labeled unsure18 findings where reading the code could not settle it, for example a server action whose reachability depends on the framework version.

By rule

RuleFindingsReal / false positive / unsurePrecision
supabase.service-role-query-without-authenticationQueries with the service role in a handler that never checks who is calling.579 / 37 / 1120% n = 46
supabase.service-role-object-access-without-tenant-scopeReads or writes a row by an id from the request without checking whose row it is (IDOR).3812 / 25 / 132% n = 37
supabase.security-definer-function-without-caller-checkHas a SECURITY DEFINER function, callable through the API, that never checks who is calling.179 / 8 / 053% n = 17
supabase.storage-policy-without-owner-checkHas a storage policy that checks only the bucket, so every user it applies to reaches every file in it.72 / 4 / 133% n = 6
supabase.mass-assignment-from-request-bodyWrites the request body into a table without an allow-list of columns.42 / 1 / 167% n = 3
supabase.user-controlled-tenant-scopeTakes the tenant or team id from the request instead of the signed-in session.40 / 0 / 4nothing decided
supabase.table-without-rlsQueries a table that has row level security turned off from a user-facing client.33 / 0 / 0100% n = 3
supabase.rls-policy-without-caller-predicateHas an RLS policy that lets rows through without checking the caller.10 / 1 / 00% n = 1

Repositories were chosen by GitHub search for Next.js + Supabase, not at random, and labeled by reading code, not by running attacks; a label can be wrong. A false positive here means a control exists that the rule missed, or the data is public by design. Rules with no row matched nothing in this scan. Method and label definitions: real-world labeled sample.

Methodology

What is counted

  • Scans stored by auditai.sh: public GitHub repositories pasted into the site, and GitHub App checks on pull requests of public repositories. Every figure comes from one read-only SQL query over scans, proof requests and votes.
  • Entry points, queries and findings use the latest scan of each repository, so re-scanning does not inflate them. "Scans run" counts every scan.
  • A finding is a match of one of the open-source rules. Nothing on this page comes from a language model.
  • Proofs come from the "Prove it" queue; votes from the buttons on report pages.

What is left out

  • Our own repositories: everything under github.com/audit0, including the intentionally vulnerable test apps we scan on every change.
  • Findings a repository suppressed with an auditai:ignore comment.
  • Private repositories: never stored, never counted. Scans in the CLI or in Claude Code and Cursor through MCP run on your machine; we never see them.
  • Anything that identifies a repository: no names, links or code on this page.

The rules, and a vulnerable and a secure test app for each, are open source: what each rule catches · rule source. Numbers are regenerated at most every five minutes.

Add your repository to these numbers

Public repositories, read-only, no sign-up.

Scan a public repository