service_role
Bypasses RLS. Completely.
One leaked service_role key reads and writes every row of every table, whatever your policies say. It ends up in client bundles more often than anyone wants to admit.
Security scanner for Supabase & AI-written code
Sentris reads your repo the way you wrote it (migrations, config, route handlers) plus your live bundle and response headers. Every finding names the exact line and carries the SQL to fix it, and no AI finding reaches you unless it can quote your file character-for-character.
No login, no signup: results in seconds. A URL scan only sees your client bundle, so it covers exposed secrets and response security headers.
Connect GitHub to scan the codeRead-only, nothing is cloned, revoke it whenever you like. Your RLS policies, storage buckets and route handlers live in the repo, and that is the only place they can be checked.
By scanning you confirm the app is yours or that you are authorised to test it. See the terms.
Scanning is free · findings & fixes from $19/month · cancel any time
$ sentris scan acme/storefront
▸ tree 1 listing · 34 files read
▸ c1 migrations · declarative schema
▸ c2 repo files · .env · live bundle
▸ c3/c4 route handlers · buckets
■ CRITICAL · confirmed
service_role key in client bundle
/_next/static/chunks/main-8c41f2.js:1
eyJ••••Rlc3Rz [masked]
■ CRITICAL · potential
table public.leads has no RLS
supabase/migrations/0002_leads.sql:3
Illustrative output · your report names your filesSee a real report →
None of these are exotic. All three are one line of config, and each one hands a stranger your whole table. If that table holds personal data, the GDPR ceiling for the resulting breach is €20 million or 4% of worldwide annual turnover, whichever is higher (Art. 83(5)), before you count the API bill nobody authorised.
service_role
One leaked service_role key reads and writes every row of every table, whatever your policies say. It ends up in client bundles more often than anyone wants to admit.
RLS off
It ships in your browser bundle, and that is correct. But a table without RLS is readable by anyone holding it, and that is everyone who opened your app.
public bucket
A public bucket serves files to anyone with the link: no key, no login, no expiry. Right for avatars. Not right for invoices.
Every finding names the file and the line, shows masked proof, and hands you SQL you can paste. Anything static analysis cannot prove is labelled potential, we would rather report one finding fewer than one that is wrong.
Fix it, hit re-scan, and the report tells you what you cleared. That loop is the product: not the list, the “resolved”.
$ sentris scan acme/storefront
▸ tree 1 listing · 34 files read
▸ c1 migrations · declarative schema
▸ c2 repo files · .env · live bundle
▸ c3/c4 route handlers · buckets
■ CRITICAL · confirmed
service_role key in client bundle
/_next/static/chunks/main-8c41f2.js:1
eyJ••••Rlc3Rz [masked]
■ CRITICAL · potential
table public.leads has no RLS
supabase/migrations/0002_leads.sql:3
fix
alter table public.leads enable row level security; create policy "Owners read their own rows" on public.leads for select to authenticated using (auth.uid() = user_id);
Most AI scanners show you what a model said. Sentris shows you what survived three attempts to kill it.
Your repo is cut into components a reviewer can hold in one head (routes, schema, client, config), and each one is read complete. No regex decides in advance what counts as interesting, which is what caps a pattern scanner at the bugs someone already thought of.
Every candidate faces independent lenses that each try to kill it: one hunts the guard that already stops it, one asks whether an attacker can reach it at all, one asks whether the consequence is the real one or a worse-sounding cousin. They never see each other's verdict, so the votes stay independent.
A surviving finding has to reproduce the line it accuses, character-for-character, from the real file. Anything the model paraphrased, misremembered or invented fails that check and is discarded before it is ever stored, never mind shown to you.
If the model half is unavailable, rate-limited or simply off, the rule checks still run and the report says exactly which stage fell over. A security tool that answers 500 when its cleverest component has a bad day is not a security tool.
AI findings are always labelled potential, sit in their own section, and never trigger a monitoring alert: a model that phrases itself differently on two runs would otherwise invent an incident at 3am.
Measured, not asserted
0
false positives across the 17 planted findings that run through the AI half, in 11 repositories, 3 of which must come back completely empty.
Measured against recorded model answers, so it is a claim about the pipeline (anchoring, verification, dedupe, assembly) and not about the model’s judgment. The page says which, because a pipeline number sold as a model number is the thing this section argues against.
The commit and the command to reproduce it →Every deep scan ships a coverage ledger: files read plus files skipped equals files in your tree. Exactly. That arithmetic is a tested property, not a progress bar. No other scanner in this category will tell you where it stopped looking.
Most scanners will not tell you what they look for. Here is the entire detection surface, what each check reads, and how sure it is allowed to be. C-checks are rules and run on every scan; A-checks are the AI pass and are always potential.
| ID | Check | Detects | Source | Confidence |
|---|---|---|---|---|
| C1 | Row Level Security | Tables in public shipping without RLS, and policies that grant using (true) | Repo: migrations & schema | potential / confirmed |
| C2 | Exposed secrets | service_role, Stripe and AI-provider keys, committed .env files | Repo files + live bundle | confirmed |
| C3 | Broken access control | Routes taking a caller-supplied id with no auth check | Repo: route handlers | potential |
| C4 | Public storage | Buckets marked public in config, a migration, or createBucket() | Repo: config, SQL, source | confirmed |
| C5 | Security headers | Missing clickjacking, HSTS and MIME-sniffing protection | Live page response | confirmed |
| C6 | RPC privilege bypass | security definer functions that never check who called them | Repo: SQL | potential |
| C7 | Mass assignment | Routes writing the whole request body, so the caller picks the columns | Repo: route handlers | potential |
| C8 | Secrets in git | .env files and private keys committed to the repository | Repo: the tree itself | confirmed |
| C9 | Plaintext storage | Passwords, API keys, IBANs and card numbers in plain text columns | Repo: SQL | potential |
| C10 | Session cookies | Session cookies set without HttpOnly, Secure or SameSite | Repo + live response | confirmed |
| C11 | Password hashing | MD5 or SHA in place of a KDF, and passwords compared as plain values | Repo: source | confirmed / potential |
| C12 | Login rate limit | Sign-in, sign-up and reset routes with nothing counting the attempts | Repo: route handlers | potential |
| C13 | Bot protection | Public forms that send mail or fill a table with no captcha and no limit | Repo: route handlers | potential |
| C14 | Query injection | SQL and PostgREST filters built by string interpolation instead of bound | Repo: source | potential |
| C15 | Unvalidated input | Request values naming the table, the columns or the sort key | Repo: route handlers | potential |
| C16 | Unescaped content | dangerouslySetInnerHTML and innerHTML with no sanitizer in the file | Repo: source | potential |
| C17 | Upload limits | Buckets and upload routes that cap neither the size nor the type | Repo: config, SQL, routes | confirmed / potential |
| C18 | Over-broad responses | select(*) returned from a table your schema says holds email, roles or tokens | Repo: routes + SQL | potential |
| C19 | Vulnerable dependencies | package.json against a curated advisory list, the Next.js middleware bypass first | Repo: package.json | confirmed / potential |
| C20 | HTTPS not enforced | Plain http, or an https page pulling scripts and forms over http | Live page response | confirmed |
| A1 | CORS with credentials | Wildcard or reflected origin sent together with credentials | Repo: AI pass | potential |
| A2 | Unscoped query | Route authenticates the caller, then queries without scoping to them | Repo: AI pass | potential |
| A3 | Unverified JWT | Token decoded and trusted without checking the signature | Repo: AI pass | potential |
| A4 | Missing rate limit | Expensive or outward-sending endpoint anyone can call in a loop | Repo: AI pass | potential |
| A5 | Leaked server secret | Server-only secret shipped to the browser via NEXT_PUBLIC_ | Repo: AI pass | potential |
Anon and publishable keys belong in your client. Rules classify by what a key can do, not by whether it looks like a secret, so the keys that are meant to be public stay quiet.
Server infrastructure & DDoS · Dependency CVEs · TLS / cryptography depth · Social engineering · Business logic beyond access control · Mobile apps · Load / performance. Stated on every report, not buried.
Your agent writes the policy. Sentris answers in the same window. An MCP server for Claude Code, Cursor and Windsurf: scan the repo, pull the findings, hand the fix prompt straight back to the thing that caused it.
Six tools, and the list is as short as it is on purpose: no create_target, no delete, no subscribe. An agent that can spend your money is a support ticket waiting for its occasion.
Set it up →$5–59 / mo
Black-box. They see what the browser sees and nothing else, so RLS, policies, buckets and unguarded routes are structurally invisible to them. Then they bury you in generic warnings.
from $19/month
Reads the repo the way you wrote it (migrations, config, route handlers), then proves what it found against your running app. Published rules, a measured false-positive rate, evidence and a fix per finding.
$350–10,000 / mo
Real depth, real pentests, real attestations, with a seat minimum and a sales call attached. The right tool once you are selling to enterprises. Not for shipping this weekend.
| Sentris | URL-only | Enterprise | Supabase advisor | |
|---|---|---|---|---|
| Reads your repo | ■ yes | ☐ no | ■ yes | ☐ no |
| Sees migrations before they ship | ■ yes | ☐ no | ◧ partly | ☐ no |
| Checks RLS & policies | ■ yes | ☐ no | ■ yes | ■ yes |
| Checks the live client bundle | ■ yes | ■ yes | ■ yes | ☐ no |
| Ships a copy-paste fix | ■ yes | ◧ partly | ◧ partly | ■ yes |
| Re-scans on a schedule | ■ yes | ■ yes | ■ yes | ☐ no |
| No sales call, no seat minimum | ■ yes | ■ yes | ☐ no | ■ yes |
Supabase's own advisor is in that table on purpose: it is free, it is good, and it is the alternative most of you already have. It reads your running database. It does not read the migration before you ship it, your client bundle, or your route handlers.
Compared in full:SnykSemgrepGitHub Advanced Securitythe Supabase Security AdvisorVibe App ScannerCheckVibeSecurityScanner.devLovable's built-in security scanner
So here is exactly what happens to it. A product that sells security has to survive its own audit first.
Scanning reads contents and metadata and writes nothing. A pull request is opened only for targets you explicitly switched it on for, never on your default branch, and never merged for you. Revoke the App in your GitHub settings whenever you like.
Files are read during the scan and gone after it. No copy of your repo exists here.
A found key is masked before it touches our database, the UI, or a log line.
One masked example as evidence. Never a full table, never a write, never a flood.
Rate-limited per address and per target host, so a scan stays inside every platform's AUP.
Unverified targets get passive checks only. Active checks need proof the asset is yours.
No login to scan, and you see the count and the grade before you pay anything. Where they are and how to close them is the product. The tiers differ in how much you watch and how often, not in how deep we look: every plan runs the same checks.
Starter
$19/month
One app, watched.
Pro
$49/month
Everything you ship.
Team
$99/month
The whole estate.
Cancel any time · billed through Stripe · you pick the plan after the scan · 14-day refund
A one-off scan tells you about today. Your app ships tomorrow: a migration lands without RLS, a key gets pasted into a client component, a bucket is flipped public to debug an upload.
Sentris re-scans on a schedule and diffs against the last run. You hear from it only when something is new. The same finding never mails you twice, because a monitor you learn to ignore protects nobody.
When a scan comes back clean, that is the product working. You are buying the quiet.
Moving between plans is a click in Stripe, and the change takes effect here by itself.
No, and it never claims to be. Sentris reports reproducible findings with evidence, at a point in time. It is not a pentest, not a certification, and carries no warranty. If you need an attestation to close an enterprise deal, you need a pentest firm, not us.
No. The repo is never cloned. Files are read during the scan and discarded when it ends. What we keep is the findings, and any secret inside them is masked before it is written anywhere.
Only if you switch it on, per repository, and it is off for every repository until you do. When it is on, Sentris opens a pull request that adds exactly one new file: a migration closing findings it confirmed against your running system. It never pushes to your default branch, never replaces an existing line, never merges anything, and never opens a pull request for something it could not prove. Secrets are excluded on purpose: a pull request that deletes a leaked key creates false safety, because the key is still compromised and only you can rotate it. Leave the switch off and Sentris behaves exactly as it did before this existed.
You get a clean result that lists exactly what was checked and what is explicitly out of scope, so you know what the result covers. Then it keeps re-scanning on your plan's schedule (weekly on Starter, nightly on Pro and Team) and alerts you the first time something new appears. The value is the ongoing 'still clean', not only the first report.
Four of the deep scan's six stages have no model in them at all: the repo is partitioned by a table of rules, candidates are deduplicated deterministically, the coverage ledger is arithmetic, and suppression is a set operation. The model appears twice: once to read a component and once, in three independent lenses, to try to disprove what the first pass claimed. After that a finding has to quote your file character-for-character or it is dropped before it is stored. And with no model key configured, a deep scan returns exactly the deterministic rule checks rather than an error: the AI half can be absent without the product being broken.
Nothing is kept here to train on: the repo is never cloned and the files are discarded when the scan ends. During the scan itself the file contents do go to a model provider. That is what an AI pass is, and any tool that runs one and tells you otherwise is worth a second look. The current provider is named on the subprocessors page rather than in this answer, so it stays accurate when it changes. If you want none of it, leave the deep scan off: the rule checks are pattern matching on our own machines and never send your code anywhere.
Two ways. Rules classify by capability, so anon and publishable keys, which belong in your client, are never flagged. And anything code alone cannot prove is labelled potential rather than sold to you as a breach. Every detector has clean and vulnerable cases, and the clean repo must return zero findings before a repo rule ships. The repo and Deep Scan corpus is also the measurement: precision 1.000 and recall 1.000 across 38 planted findings in 11 repositories, published in full at /precision with the date, the commit and the command to reproduce it. Nobody else in this category publishes theirs.
Because the advisor reads your running database, and that is only one of the places these mistakes live. It cannot see the migration sitting in your branch before it ships, it cannot see the service_role key your client bundle is serving to every visitor, and it cannot see a route handler that takes an id and never checks who is asking. It also does not watch: it answers when you open it. Sentris covers all four surfaces and re-checks them on a schedule. If missing RLS is genuinely your only concern, use the advisor: it is free and it is good.
Every exposure the deterministic checks find, each with the exact file and line, masked evidence, and a copy-paste fix, plus re-scans on a schedule that alert you the first time something new appears. Scanning itself is free and needs no login: you see the count and the grade before you pay anything. What you are paying for is where they are and how to close them. Worth saying plainly: a URL-only scan reaches four of the checks, because the rest of them live in code a browser never sees. Connect the repo and a free scan runs all twenty rule checks instead of four, still without paying. The AI pass is the part that needs a subscription, because it spends tokens. No trial maze, no upsell ladder.
Yes, the whole thing, at /sample. It is an unedited scan of a deliberately vulnerable Supabase app we maintain, rendered by the same code that renders a customer's report: every finding, every file and line, the masked evidence and the SQL fix. We publish that repo rather than a customer's, and nothing on the page is trimmed for the screenshot.
One person, building on the same stack you do: Supabase, Next.js, shipped in evenings and weekends. Sentris is not a company with a security team behind it, and pretending otherwise would be the first thing worth distrusting about a security tool. What it is: focused checks that are published in full, run against fixed clean and vulnerable cases before they ship, and honest about the difference between what they proved and what they suspect.
Any time, from Stripe. Access follows your subscription status automatically, so there is nothing to email us about.
Our precision and recall, in public11 repos, 38 planted findings, measured 2026-08-14
Free security rules for your AI coding toolthe mistake not made needs no fix
The scan takes seconds and costs nothing. If it comes back clean you have lost a minute. If it does not, you found the thing before it found you.
No login, no signup: results in seconds. A URL scan only sees your client bundle, so it covers exposed secrets and response security headers.
Connect GitHub to scan the codeRead-only, nothing is cloned, revoke it whenever you like. Your RLS policies, storage buckets and route handlers live in the repo, and that is the only place they can be checked.
By scanning you confirm the app is yours or that you are authorised to test it. See the terms.
No login to scan · findings & fixes from $19/month · cancel any time