[{"data":1,"prerenderedAt":811},["ShallowReactive",2],{"blog-en-supabase-security-checker":3,"blog-index-en":571},{"id":4,"title":5,"body":6,"category":523,"cover":524,"coverAlt":525,"description":526,"draft":527,"extension":528,"faq":529,"image":545,"keywords":546,"meta":559,"navigation":560,"ogTitle":561,"path":562,"published":563,"seo":564,"stem":565,"tldr":566,"updated":563,"__hash__":570},"blog_en\u002Fblog\u002Fsupabase-security-checker.md","Supabase security checker: run the five checks yourself",{"type":7,"value":8,"toc":511},"minimark",[9,13,21,24,29,32,35,38,47,51,54,66,90,113,116,119,125,129,132,142,149,160,163,227,232,238,261,269,273,276,282,288,291,302,323,331,335,338,345,351,358,364,374,378,381,387,390,397,401,404,410,429,435,445,448,452,483,488,496,503],[10,11,12],"p",{},"You searched for a Supabase security checker and got nine of them back. One\nwants your repository. One wants a browser extension. One wants read access to\nyour whole Supabase account, which is close enough to the thing you were worried\nabout to give you pause.",[10,14,15,16,20],{},"Here is what none of those tool pages says out loud. ",[17,18,19],"strong",{},"There are five checks,\nand you can run every one of them yourself",", with no account, no install, and\nnone of the credentials that would be dangerous to hand over. Ten minutes in a\nterminal covers the same ground the tools do.",[10,22,23],{},"The five below are the ones our own scanner runs against a Supabase project,\nwritten out as commands. If you built with Lovable, Bolt, v0, Cursor, Replit,\nWindsurf or Base44 and your app stores anything at all, the database behind it\nis very likely Supabase, and these are the questions a stranger would put to it.",[25,26,28],"h2",{"id":27},"what-does-a-supabase-security-checker-actually-check","What does a Supabase security checker actually check?",[10,30,31],{},"Your published app. The settings inside your project are a different tool's job,\nand Supabase already ships that tool.",[10,33,34],{},"The Security Advisor in your dashboard reads your project's own catalogue: which\ntables have Row Level Security switched off, which functions have a loose search\npath, which extensions sit in the public schema. It is reading what you\nconfigured.",[10,36,37],{},"A checker reads what your app hands a stranger. The JavaScript bundle your\nvisitors download, the answer a table gives a request with no login attached,\nthe contents of a storage bucket, the headers on your pages. It sees none of\nyour configuration and does not need to, because it is looking at the\nconsequence of it.",[10,39,40,41,46],{},"The two halves come apart more often than the names suggest. A table can pass\nthe Advisor with the switch on and a policy written, and still hand its rows to\nanybody, because the policy that got written allows everybody. That is worth\nreading about on its own if it is new to you:\n",[42,43,45],"a",{"href":44},"\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","the toggle is not the protection",".",[25,48,50],{"id":49},"check-1-which-supabase-key-does-your-app-ship","Check 1: which Supabase key does your app ship?",[10,52,53],{},"Open your app in a browser, press F12, and look at one request.",[10,55,56,57,61,62,65],{},"Go to the Network tab, reload the page, and type ",[58,59,60],"code",{},"supabase"," into the filter box.\nYour app will make at least one request to an address ending ",[58,63,64],{},".supabase.co",".\nClick it. Two things you need for the rest of this article are in there:",[67,68,69,80],"ul",{},[70,71,72,75,76,79],"li",{},[17,73,74],{},"The request address"," begins with your project URL, something like\n",[58,77,78],{},"https:\u002F\u002Fabcdefghij.supabase.co",". Checks 2 and 3 are aimed at it.",[70,81,82,89],{},[17,83,84,85,88],{},"The ",[58,86,87],{},"apikey"," request header"," carries the key your app hands every visitor.",[10,91,92,93,96,97,100,101,104,105,108,109,46],{},"Copy both somewhere, then read the key itself. One beginning ",[58,94,95],{},"sb_publishable_","\nbelongs in your app and there is nothing here to fix. One beginning ",[58,98,99],{},"sb_secret_","\nnever does, and finding it ends the checklist early: rotate it today, ahead of\neverything else on this page. Older projects carry a pair with no prefix to\nread, ",[58,102,103],{},"anon"," and ",[58,106,107],{},"service_role",", and telling those two apart means\n",[42,110,112],{"href":111},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","reading the role inside the key",[10,114,115],{},"That last one is rare. Across 30,998 live apps we scanned in August 2026, a\nSupabase secret key sitting in the browser turned up three times.",[10,117,118],{},"While the Network tab is open, write down the table names you can see in those\nrequest addresses. The next check needs them.",[120,121],"diagram",{"alt":122,"caption":123,"src":124},"A bundle pane in the centre holding a key and an address. An arrow leaves it to the left towards a browser window, tagged with the numerals 4 and 5. Another leaves to the right towards a database and a storage bucket labelled .supabase.co, tagged 2 and 3.","Everything below is aimed at one of two addresses: your app's own, and your project's. Check 1 is what gets you the second one.","\u002Fblog\u002Fsupabase-security-checker\u002Ftwo-addresses-1600x680.png",[25,126,128],{"id":127},"check-2-will-your-tables-answer-a-stranger","Check 2: will your tables answer a stranger?",[10,130,131],{},"Ask the database how many rows it would hand somebody with no login, and read\nthe number off the response.",[133,134,140],"pre",{"className":135,"code":137,"language":138,"meta":139},[136],"language-bash","curl -s -i \\\n  -H \"apikey: YOUR_PUBLISHABLE_KEY\" \\\n  -H \"Prefer: count=exact\" \\\n  -H \"Range: 0-0\" \\\n  \"https:\u002F\u002FYOUR_PROJECT.supabase.co\u002Frest\u002Fv1\u002Fprofiles?select=count\" \\\n  | grep -i content-range\n","bash","",[58,141,137],{"__ignoreMap":139},[10,143,144,145,148],{},"Swap in the key and the project address from check 1, and put your own table\nname where ",[58,146,147],{},"profiles"," is. curl comes installed on macOS, on Linux and on\ncurrent Windows, so there is nothing to fetch first.",[10,150,151,152,155,156,159],{},"No row is fetched by that request. ",[58,153,154],{},"select=count"," asks for a count,\n",[58,157,158],{},"Range: 0-0"," asks for none of the rows themselves, and the whole answer arrives\nin a single header. It is the same request our scanner makes, which is why the\nscanner can run against somebody's live app without touching their data.",[10,161,162],{},"Four things can come back, and one of them is a finding:",[164,165,166,179],"table",{},[167,168,169],"thead",{},[170,171,172,176],"tr",{},[173,174,175],"th",{},"What prints",[173,177,178],{},"What it means",[180,181,182,193,203,217],"tbody",{},[170,183,184,190],{},[185,186,187],"td",{},[58,188,189],{},"content-range: *\u002F0",[185,191,192],{},"Nothing readable without a login. That table is doing its job.",[170,194,195,200],{},[185,196,197],{},[58,198,199],{},"content-range: 0-0\u002F128",[185,201,202],{},"128 rows reachable by anyone holding the key that ships in your page.",[170,204,205,214],{},[185,206,207,210,211],{},[58,208,209],{},"401"," or ",[58,212,213],{},"403",[185,215,216],{},"The key was turned away before the table was reached. Unanswered, not clean.",[170,218,219,224],{},[185,220,221],{},[58,222,223],{},"404",[185,225,226],{},"No table by that name. Either you guessed, or it is spelled differently.",[120,228],{"alt":229,"caption":230,"src":231},"Two response panes side by side. Both show an identical 200 status line. The left one boxes a content-range header ending in a slash and a zero, and takes a green tick; the right one boxes the same header ending in 128, and takes a red cross.","Both requests succeeded. One header is the entire difference between a table doing its job and a table handing rows to anyone who asks.","\u002Fblog\u002Fsupabase-security-checker\u002Fthe-one-header-1600x700.png",[10,233,234,235,237],{},"The refusal is the row to be careful with. A ",[58,236,209],{}," says the request stopped\nbefore it ever reached the table, which tells you nothing about whether the\ntable is protected, and it is the easiest of the four to round up into a tick.",[10,239,240,241,244,245,244,247,244,250,253,254,244,257,260],{},"Run the command for every table your app named in check 1, then for the ordinary\nones an app tends to have: ",[58,242,243],{},"users",", ",[58,246,147],{},[58,248,249],{},"customers",[58,251,252],{},"orders",",\n",[58,255,256],{},"messages",[58,258,259],{},"invoices",". Those six are where somebody else's data lives.",[10,262,263,264,268],{},"This is the check that finds the most. Of the 3,680 Supabase-backed apps where\nwe could complete it, 2,096 had at least one table answering a request with no\nlogin, and in 394 of those the open table was named after people. The\n",[42,265,267],{"href":266},"\u002Fblog\u002Fcan-anyone-read-your-supabase-database","full measurement and what it is a share of","\nis a separate article.",[25,270,272],{"id":271},"check-3-will-a-storage-bucket-list-its-own-files","Check 3: will a storage bucket list its own files?",[10,274,275],{},"Ask a bucket for its contents and see whether any come back.",[133,277,280],{"className":278,"code":279,"language":138,"meta":139},[136],"curl -s -X POST \\\n  -H \"apikey: YOUR_PUBLISHABLE_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"prefix\":\"\",\"limit\":1}' \\\n  \"https:\u002F\u002FYOUR_PROJECT.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Flist\u002Favatars\"\n",[58,281,279],{"__ignoreMap":139},[10,283,284,287],{},[17,285,286],{},"Send the body."," A POST to that address with nothing in it comes back \"Body\ncannot be empty\" for every bucket in every project, no matter how they are\nconfigured. We know because our own bucket check shipped that way and spent\nmonths reporting cheerfully that nothing was listable.",[10,289,290],{},"Two shapes of answer:",[67,292,293,299],{},[70,294,295,298],{},[58,296,297],{},"[]"," means the bucket is locked, or there is no bucket by that name. From\noutside you cannot tell which, so an empty list is not evidence of anything.",[70,300,301],{},"An array with a file in it means a stranger can ask your project for a\ndirectory of that bucket and get one.",[10,303,304,305,244,308,253,311,244,314,244,317,244,320,46],{},"Try the bucket names your app used, then the common ones: ",[58,306,307],{},"avatars",[58,309,310],{},"public",[58,312,313],{},"uploads",[58,315,316],{},"files",[58,318,319],{},"images",[58,321,322],{},"documents",[10,324,325,326,330],{},"Listable and public are two different settings kept in two different places, and\n",[42,327,329],{"href":328},"\u002Fblog\u002Fsupabase-storage-bucket-public","the difference decides how much a public bucket matters",".\nListing is the one to close, because it saves a stranger the work of guessing a\nfilename. Of the 27,269 apps where we could ask, 792 answered with a list.",[25,332,334],{"id":333},"check-4-is-your-source-code-published-alongside-your-app","Check 4: is your source code published alongside your app?",[10,336,337],{},"Read the last line of your JavaScript bundle.",[10,339,340,341,344],{},"Back in the Network tab from check 1, find the largest ",[58,342,343],{},".js"," file your app\nloaded and copy its address. Then:",[133,346,349],{"className":347,"code":348,"language":138,"meta":139},[136],"curl -s \"https:\u002F\u002Fyour-app.example\u002Fassets\u002Findex-abc123.js\" | tail -c 120\n",[58,350,348],{"__ignoreMap":139},[10,352,353,354,357],{},"A last line reading ",[58,355,356],{},"\u002F\u002F# sourceMappingURL=index-abc123.js.map"," means your build\nwrote a source map and told browsers where it is. Whether it actually published\nit takes one more request:",[133,359,362],{"className":360,"code":361,"language":138,"meta":139},[136],"curl -s -o \u002Fdev\u002Fnull -w \"%{http_code}\\n\" \\\n  \"https:\u002F\u002Fyour-app.example\u002Fassets\u002Findex-abc123.js.map\"\n",[58,363,361],{"__ignoreMap":139},[10,365,366,369,370,46],{},[58,367,368],{},"200"," means anyone can download it. A source map turns the compressed\nJavaScript back into your original files, with your folder structure, your\ncomments and your logic intact. What it exposes is your code, and any key it\nreveals was already sitting in the bundle beside it, which is what check 1 was\nfor. 3,885 of the 30,987 apps we could check publish theirs, and on some\nbuilders that is a platform default rather than a decision anybody made:\n",[42,371,373],{"href":372},"\u002Fblog\u002Fsource-maps-exposed-in-production","what a published map shows",[25,375,377],{"id":376},"check-5-what-your-host-sends-with-every-page","Check 5: what your host sends with every page",[10,379,380],{},"One request to your app's own address, reading what came back with it.",[133,382,385],{"className":383,"code":384,"language":138,"meta":139},[136],"curl -s -i \"https:\u002F\u002Fyour-app.example\" | grep -i -E \\\n  \"content-security-policy|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy\"\n",[58,386,384],{"__ignoreMap":139},[10,388,389],{},"Five headers, and the ones that do not print are the ones you are missing. They\ntell a browser to refuse the page if it is loaded inside somebody else's site,\nto insist on an encrypted connection next time, and to stop guessing at what\nkind of file it just received.",[10,391,392,393,46],{},"Almost every app fails this one and almost nobody can act on it. 30,756 of the\n30,981 apps we could check were missing at least one, because the hosting\nplatform sends these and an owner on a builder subdomain has nowhere to change\nthem. We count it and cap it at medium:\n",[42,394,396],{"href":395},"\u002Fblog\u002Fmissing-security-headers","what missing headers do and do not predict",[25,398,400],{"id":399},"reading-what-you-found","Reading what you found",[10,402,403],{},"Severity order, which is not the order you ran them in.",[10,405,406,409],{},[17,407,408],{},"A secret key in the bundle comes first."," It skips every rule you wrote on\nevery table, so rotate it before you look at anything else here.",[10,411,412,415,416,244,418,253,420,104,422,424,425,428],{},[17,413,414],{},"A table full of people answering check 2 is next."," ",[58,417,243],{},[58,419,147],{},[58,421,252],{},[58,423,256],{}," hold somebody else's data and they are reachable right\nnow. A ",[58,426,427],{},"products"," table answering the same way may be exactly right, and you are\nthe only person who can tell the two apart.",[10,430,431,434],{},[17,432,433],{},"Then a listable bucket, then maps and headers",", which are usually your\nplatform's defaults and not something you chose.",[10,436,437,438,441,442,444],{},"One thing to carry out of all five. ",[17,439,440],{},"A check that got no answer has not\npassed."," A ",[58,443,209],{},", a request that timed out, a table whose name you guessed\nwrong: each of those is a question still open. Our scanner prints \"Couldn't\ncheck\" on those lines and leaves the grade alone, and doing this by hand means\nkeeping that list yourself.",[10,446,447],{},"Everything here also describes the app that was live at the moment you asked.\nRow Level Security gets switched off to make a page load, a bucket gets opened\nfor one upload, a key gets pasted in so a feature ships tonight.",[25,449,451],{"id":450},"what-to-do-this-week","What to do this week",[453,454,455],"key-takeaways",{},[67,456,457,471,474,477,480],{},[70,458,459,460,244,462,244,464,244,466,104,468,470],{},"Run check 2 against every table your app names, then against ",[58,461,243],{},[58,463,147],{},[58,465,249],{},[58,467,252],{},[58,469,256],{},". That is where the findings are.",[70,472,473],{},"If check 1 turned up a secret key, rotate it first. Deleting it from your code leaves the old value working for whoever already has it.",[70,475,476],{},"Write down every probe that got a refusal or no reply. Those are unanswered, and an unanswered question is not a clean result.",[70,478,479],{},"Leave the tables meant to be public alone. A product list answering a stranger is your app working correctly.",[70,481,482],{},"Run all five again after a deploy and after anyone changes a database rule. Neither one announces itself.",[120,484],{"alt":485,"caption":486,"src":487},"A Reeve report card. A large amber tile carrying the letter C, the number 85 out of 100 under it, and nine rows to the right, each with a check icon and a bar. Seven rows carry a green tick, the storage row carries a red cross, and the database row carries an empty dashed slot instead of a mark.","The same five checks, plus four more, reported back. The score is 85 and the grade is a C, because one high finding caps the letter whatever the arithmetic says. The dashed row is the database, which never answered.","\u002Fblog\u002Fsupabase-security-checker\u002Fgrade-score-and-checks-1600x760.png",[10,489,490,491,495],{},"Our own ",[42,492,494],{"href":493},"\u002Fsecurity-scanner","free security scanner"," runs these five and four more\nagainst any live URL in about twenty seconds, with no account and no credentials\nhanded over. It reads, never writes, and where it cannot get an answer it says\nso on the line.",[10,497,498,499,46],{},"You can do all five yourself today. What no single pass can tell you is whether\nthe answers are still the same next month, which is what we built Reeve Care to\ndo: it re-runs these checks on a schedule, emails you when an answer gets worse,\nand keeps verified backups of your Supabase database, plus the files your users\nuploaded once you connect a storage credential.\n",[42,500,502],{"href":501},"\u002F#pricing","What it watches and what it costs",[10,504,505,506,510],{},"If a terminal is not where you want to be, the plain-language walkthrough for\n",[42,507,509],{"href":508},"\u002Fis-your-supabase-app-safe","Supabase apps"," covers what each of these settings\nis for and where to find it in the dashboard.",{"title":139,"searchDepth":512,"depth":512,"links":513},3,[514,516,517,518,519,520,521,522],{"id":27,"depth":515,"text":28},2,{"id":49,"depth":515,"text":50},{"id":127,"depth":515,"text":128},{"id":271,"depth":515,"text":272},{"id":333,"depth":515,"text":334},{"id":376,"depth":515,"text":377},{"id":399,"depth":515,"text":400},{"id":450,"depth":515,"text":451},"Security basics","\u002Fblog\u002Fsupabase-security-checker\u002Fcover-1200x630.png","A terminal window seen straight on, five short commands typed under an accent prompt, and a caret waiting on the sixth line.","A Supabase security checker reads your published app instead of your project settings. Here are the five checks it runs, and how to run each one yourself.",false,"md",[530,533,536,539,542],{"q":531,"a":532},"Does the Supabase Security Advisor catch everything?","It catches everything in its own half. The Advisor reads your project's configuration and reports tables with Row Level Security switched off, functions with a loose search path, and similar settings you control from the dashboard. What it cannot see is your published app: which key ended up in the JavaScript your visitors download, whether a policy you wrote actually stops an anonymous request, or what headers your host sends. Run the Advisor and the five checks in this article, because they look at different things.",{"q":534,"a":535},"Is running these checks against my own project safe?","Yes. Every command here reads and none of them writes. The table check asks your database for a count of rows and reads the number out of a response header, so no row is ever fetched. The bucket check asks for a listing and stops there, without downloading a file. The last two are an ordinary page request of the kind your visitors make all day. Running them against a project you do not own is a different question, and the answer is to ask the owner first.",{"q":537,"a":538},"My anon key is in my bundle. Is that a problem?","No. The anon key in older projects, and sb_publishable_ in newer ones, is designed to sit in the code your visitors download. It names your project and grants nothing by itself; Row Level Security is what decides which rows a request gets back. The key that must never be there is the secret one, called service_role in older projects and sb_secret_ in newer ones, because it skips every rule you wrote.",{"q":540,"a":541},"What does content-range: *\u002F0 mean?","It is your database saying it will hand this caller zero rows from that table. The star means the response contains no rows at all, and the number after the slash is the count the caller is allowed to see. So *\u002F0 is the answer you want from a table holding personal data, and 0-0\u002F128 means 128 rows are reachable by anyone holding the key that ships in your app.",{"q":543,"a":544},"Do I need my service_role key to test this?","No, and a checker that asks for one is asking for the wrong thing. Every check here uses the publishable key that is already in your app, because that is the key a stranger would have. Testing with a secret key tells you what an administrator can reach, which was never in question. Do not paste a service_role or sb_secret_ key into any scanner, ours included: nothing we run needs it.","\u002Fblog\u002Fsupabase-security-checker\u002Fcard-800x500.png",[547,548,549,550,551,552,553,554,555,556,557,558],"supabase security checker","supabase security scanner","supabase rls checker","supabase leak scanner","supabase security audit","check supabase rls","test supabase row level security","supabase security advisor","supabase exposed tables","is my supabase database public","supabase anon key exposed","supabase storage bucket public check",{},true,"Supabase security checker: the five checks","\u002Fblog\u002Fsupabase-security-checker","2026-09-06",{"title":5,"description":526},"blog\u002Fsupabase-security-checker",[567,568,569],"A Supabase security checker reads your live app the way a stranger does: the bundle it ships, the answers your tables give, your storage buckets, your headers.","Supabase's own Security Advisor reads the other half, which is your project's configuration. Neither one sees what the other does.","Five checks cover the outside view. Every one of them runs from a terminal, needs no account, and takes about ten minutes together.","hC9T0JTs5O-_QD19pH-LXAcW6FF7LJQv5RJuU3tNcBA",[572,579,585,591,597,603,609,615,621,627,633,639,645,651,657,663,669,675,676,682,687,693,699,705,711,717,723,729,735,741,747,753,758,764,770,775,779,785,791,796,801,807],{"path":573,"title":574,"description":575,"published":576,"category":577,"image":578,"draft":527},"\u002Fblog\u002Fsupabase-backup-auth-users","Why your Supabase dump has no users in it","Run supabase db dump on its own and you get the shape of your database and none of its rows, with the auth schema your users live in left out entirely.","2026-09-23","Backups","\u002Fblog\u002Fsupabase-backup-auth-users\u002Fcard-800x500.png",{"path":580,"title":581,"description":582,"published":583,"category":523,"image":584,"draft":527},"\u002Fblog\u002Fdomain-and-certificate-expiry","Domain expired, website down: what actually happens next","Your domain expired and your website is down. Here is the clock you are on, why a lapsed certificate is the easier of the two, and how to check both.","2026-09-22","\u002Fblog\u002Fdomain-and-certificate-expiry\u002Fcard-800x500.png",{"path":586,"title":587,"description":588,"published":589,"category":523,"image":590,"draft":527},"\u002Fblog\u002Fis-lovable-safe","Is Lovable safe? What 18,554 live Lovable apps showed","Is Lovable safe? We ran nine checks on 18,554 live Lovable apps. The platform was the cleanest of five builders. Every finding was inside the app itself.","2026-09-21","\u002Fblog\u002Fis-lovable-safe\u002Fcard-800x500.png",{"path":592,"title":593,"description":594,"published":595,"category":523,"image":596,"draft":527},"\u002Fblog\u002Fvibe-coded-app-security-checklist","The vibe coding security checklist, in nine checks","A vibe coding security checklist with nine items, each one something anyone can verify about your live app from outside, and each with a one-line test.","2026-09-20","\u002Fblog\u002Fvibe-coded-app-security-checklist\u002Fcard-800x500.png",{"path":598,"title":599,"description":600,"published":601,"category":523,"image":602,"draft":527},"\u002Fblog\u002Fstripe-secret-key-in-frontend","A Stripe secret key exposed in your frontend can move money","A Stripe secret key exposed in your frontend can refund, charge and read every customer record you hold. Your pk_live_ key is meant to be there.","2026-09-19","\u002Fblog\u002Fstripe-secret-key-in-frontend\u002Fcard-800x500.png",{"path":604,"title":605,"description":606,"published":607,"category":523,"image":608,"draft":527},"\u002Fblog\u002Fvite-and-next-public-env-vars","Vite env variables exposed: VITE_ and NEXT_PUBLIC_ mean publish this","Vite env variables exposed in your app did what the prefix asked. VITE_ and NEXT_PUBLIC_ mean publish this, and the AI that added one never knew the cost.","2026-09-18","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fcard-800x500.png",{"path":610,"title":611,"description":612,"published":613,"category":523,"image":614,"draft":527},"\u002Fblog\u002Fbase44-source-maps","Base44 security: what a scan flags, and what is yours to fix","Base44 security on 5,438 scanned apps: three findings on nearly every one are the platform's, the source map is Base44's badge, and what is yours is short.","2026-09-17","\u002Fblog\u002Fbase44-source-maps\u002Fcard-800x500.png",{"path":616,"title":617,"description":618,"published":619,"category":523,"image":620,"draft":527},"\u002Fblog\u002Fis-cursor-ai-safe","Is Cursor AI safe? The editor, the code, and the app you shipped","Is Cursor AI safe? Three questions in one search: what Cursor keeps, what the code it writes gets wrong, and whether the app you shipped is open.","2026-09-16","\u002Fblog\u002Fis-cursor-ai-safe\u002Fcard-800x500.png",{"path":622,"title":623,"description":624,"published":625,"category":523,"image":626,"draft":527},"\u002Fblog\u002Fis-replit-safe","Is Replit safe? What we found in 3,042 live Replit apps","Is Replit safe? We ran nine external checks on 3,042 live Replit apps. The host was not where the findings were. The app each owner published was.","2026-09-15","\u002Fblog\u002Fis-replit-safe\u002Fcard-800x500.png",{"path":628,"title":629,"description":630,"published":631,"category":577,"image":632,"draft":527},"\u002Fblog\u002Fsupabase-storage-backup","Supabase storage backup: why your database copy has no files","A Supabase storage backup is a separate job. Database backups keep the list of your files and none of the files, so a restore leaves every upload broken.","2026-09-14","\u002Fblog\u002Fsupabase-storage-backup\u002Fcard-800x500.png",{"path":634,"title":635,"description":636,"published":637,"category":577,"image":638,"draft":527},"\u002Fblog\u002Fsupabase-point-in-time-recovery","Supabase point-in-time recovery: what it costs, what it misses","Supabase point-in-time recovery rewinds your database to any second in the last week. It costs $100 a month on top of Pro, and it covers your database only.","2026-09-13","\u002Fblog\u002Fsupabase-point-in-time-recovery\u002Fcard-800x500.png",{"path":640,"title":641,"description":642,"published":643,"category":577,"image":644,"draft":527},"\u002Fblog\u002Fsupabase-project-paused-recover","Supabase project paused? Your data is still there","Supabase paused your project after a week of inactivity. Nothing is deleted, Restore sits beside the project name, and you have a year before that changes.","2026-09-12","\u002Fblog\u002Fsupabase-project-paused-recover\u002Fcard-800x500.png",{"path":646,"title":647,"description":648,"published":649,"category":523,"image":650,"draft":527},"\u002Fblog\u002Fsafest-ai-app-builder","Which AI app builder is safest? We scanned 30,998 apps","Which AI app builder is safest? We scanned 30,998 live apps from Lovable, Base44, Replit, v0 and Bolt. The builder is not what decides your grade.","2026-09-11","\u002Fblog\u002Fsafest-ai-app-builder\u002Fcard-800x500.png",{"path":652,"title":653,"description":654,"published":655,"category":523,"image":656,"draft":527},"\u002Fblog\u002Fenable-rls-on-every-supabase-table","Enable Row Level Security on every Supabase table, then prove it","Enabling Row Level Security in Supabase with no policy locks a table completely. A policy without the setting does nothing. Here is the SQL, and the test.","2026-09-10","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Fcard-800x500.png",{"path":658,"title":659,"description":660,"published":661,"category":523,"image":662,"draft":527},"\u002Fblog\u002Fsupabase-rls-disabled-in-public","Supabase \"RLS disabled in public\": what the warning misses","Supabase reports \"RLS disabled in public\" as an error. It says nothing about the read policy that leaves your table just as open to strangers.","2026-09-09","\u002Fblog\u002Fsupabase-rls-disabled-in-public\u002Fcard-800x500.png",{"path":664,"title":665,"description":666,"published":667,"category":523,"image":668,"draft":527},"\u002Fblog\u002Frotate-supabase-service-role-key","How to rotate a leaked Supabase service_role key","Supabase says fix the leak first. Other guides say rotate now. Which is right depends on where your service_role key leaked.","2026-09-08","\u002Fblog\u002Frotate-supabase-service-role-key\u002Fcard-800x500.png",{"path":670,"title":671,"description":672,"published":673,"category":523,"image":674,"draft":527},"\u002Fblog\u002Fvibe-coding-security-scanners-compared","Vibe coding security scanners compared, including ours","The best vibe coding security scanner comes down to three questions no feature list answers. Ten tools compared, with prices, and the jobs Reeve does not do.","2026-09-07","\u002Fblog\u002Fvibe-coding-security-scanners-compared\u002Fcard-800x500.png",{"path":562,"title":5,"description":526,"published":563,"category":523,"image":545,"draft":527},{"path":677,"title":678,"description":679,"published":680,"category":523,"image":681,"draft":527},"\u002Fblog\u002Fvibe-coding-security-scanner","Vibe coding security scanner: what a URL scan misses","A vibe coding security scanner reads your live app from outside. Here is what that covers, the four things it cannot see, and how to read the result.","2026-09-05","\u002Fblog\u002Fvibe-coding-security-scanner\u002Fcard-800x500.png",{"path":395,"title":683,"description":684,"published":685,"category":523,"image":686,"draft":527},"Missing security headers: when it actually matters","Missing security headers is the finding our scanner prints most. Here is what it protects against, and when it is the least urgent line on your report.","2026-09-04","\u002Fblog\u002Fmissing-security-headers\u002Fcard-800x500.png",{"path":688,"title":689,"description":690,"published":691,"category":523,"image":692,"draft":527},"\u002Fblog\u002Fopenai-api-key-exposed-in-frontend","Your OpenAI API key is exposed in your frontend. Rotate it.","An OpenAI API key exposed in your frontend cannot be locked to a domain. Rotate it today, move the call behind your own endpoint, and cap the spend.","2026-09-03","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":694,"title":695,"description":696,"published":697,"category":523,"image":698,"draft":527},"\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps","An API key exposed in your frontend: what 30,998 apps shipped","An API key exposed in your frontend is usually a Google Maps key. We scanned 30,998 live vibe-coded apps and counted which secrets actually leak.","2026-09-02","\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps\u002Fcard-800x500.png",{"path":700,"title":701,"description":702,"published":703,"category":577,"image":704,"draft":527},"\u002Fblog\u002Fsupabase-backup-tools-compared","Supabase backup tools compared, including ours","Four kinds of Supabase backup tool, what each one actually copies, and the case where a free GitHub Action beats paying anyone, us included.","2026-09-01","\u002Fblog\u002Fsupabase-backup-tools-compared\u002Fcard-800x500.png",{"path":706,"title":707,"description":708,"published":709,"category":523,"image":710,"draft":527},"\u002Fblog\u002Freplit-secrets-explained","How to use secrets in Replit, and what still gets published","How to use secrets in Replit: add one, read it back, and fix the two reasons it comes back undefined. Plus the keys the Secrets tool cannot keep private.","2026-08-31","\u002Fblog\u002Freplit-secrets-explained\u002Fcard-800x500.png",{"path":712,"title":713,"description":714,"published":715,"category":577,"image":716,"draft":527},"\u002Fblog\u002Fback-up-supabase-free-tier","Supabase free tier backups: how to make one without a terminal","There are no Supabase free tier backups, so the copy has to come from you. How to make one from the dashboard, and what CSV leaves out.","2026-08-30","\u002Fblog\u002Fback-up-supabase-free-tier\u002Fcard-800x500.png",{"path":718,"title":719,"description":720,"published":721,"category":523,"image":722,"draft":527},"\u002Fblog\u002Fis-supabase-secure","Is Supabase secure? Yes. Your project is a separate question","Is Supabase secure? The platform is audited, encrypted and pen-tested. Their own compliance documents say where that stops and your settings begin.","2026-08-29","\u002Fblog\u002Fis-supabase-secure\u002Fcard-800x500.png",{"path":724,"title":725,"description":726,"published":727,"category":523,"image":728,"draft":527},"\u002Fblog\u002Fwhere-to-find-supabase-api-keys","Where to find your Supabase API keys: anon, service_role and the URL","Your Supabase project URL, anon key and service_role key are on one dashboard page. Here is where that page is, and which of the four belongs in your app.","2026-08-28","\u002Fblog\u002Fwhere-to-find-supabase-api-keys\u002Fcard-800x500.png",{"path":730,"title":731,"description":732,"published":733,"category":523,"image":734,"draft":527},"\u002Fblog\u002Fnew-row-violates-row-level-security-policy","New row violates row-level security policy in Supabase. Now what?","\"New row violates row-level security policy\" means Supabase refused a write. The fix that clears it in ten seconds also reopens the table to everyone.","2026-08-27","\u002Fblog\u002Fnew-row-violates-row-level-security-policy\u002Fcard-800x500.png",{"path":736,"title":737,"description":738,"published":739,"category":523,"image":740,"draft":527},"\u002Fblog\u002Fcors-wildcard-security-risk","Is a CORS wildcard a security risk? Usually not.","Is a CORS wildcard a security risk? Usually it is your builder default, and it gives away nothing your server was not already handing to anyone who asked.","2026-08-26","\u002Fblog\u002Fcors-wildcard-security-risk\u002Fcard-800x500.png",{"path":742,"title":743,"description":744,"published":745,"category":577,"image":746,"draft":527},"\u002Fblog\u002Fsupabase-branching-is-not-a-backup","Supabase branching is not a backup. It only goes forwards.","Supabase branching is not a backup: a branch starts with none of your data, and merging only moves schema. What it is for, and what to use instead.","2026-08-25","\u002Fblog\u002Fsupabase-branching-is-not-a-backup\u002Fcard-800x500.png",{"path":748,"title":749,"description":750,"published":751,"category":577,"image":752,"draft":527},"\u002Fblog\u002Fhow-to-restore-a-supabase-backup","How to restore a Supabase backup, and what breaks after","How to restore a Supabase backup from the dashboard or from a dump file, what the restore replaces, and why your app can still be broken when it finishes.","2026-08-24","\u002Fblog\u002Fhow-to-restore-a-supabase-backup\u002Fcard-800x500.png",{"path":328,"title":754,"description":755,"published":756,"category":523,"image":757,"draft":527},"Your Supabase storage bucket is public. Is that a problem?","A public Supabase storage bucket means anyone with a file URL can open it. It does not mean anyone can list what is in there. Two different settings.","2026-08-23","\u002Fblog\u002Fsupabase-storage-bucket-public\u002Fcard-800x500.png",{"path":759,"title":760,"description":761,"published":762,"category":523,"image":763,"draft":527},"\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend","Is a Google API key exposed in your frontend a problem?","A Google API key exposed in your frontend is the key our scanner finds most often, and usually it is fine. One free setting decides which it is.","2026-08-22","\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":765,"title":766,"description":767,"published":768,"category":577,"image":769,"draft":527},"\u002Fblog\u002Fai-agent-deleted-my-database","An AI agent deleted my Supabase data. What can I recover?","An AI agent deleted your database data. What you can recover was decided before it ran, and the next few minutes decide how much of it survives.","2026-08-21","\u002Fblog\u002Fai-agent-deleted-my-database\u002Fcard-800x500.png",{"path":266,"title":771,"description":772,"published":773,"category":523,"image":774,"draft":527},"Can anyone read your Supabase database? We checked 3,680 apps","Can anyone read your Supabase database without logging in? We scanned 30,998 live apps built with AI builders and measured how often the answer is yes.","2026-08-18","\u002Fblog\u002Fcan-anyone-read-your-supabase-database\u002Fcard-800x500.png",{"path":372,"title":776,"description":777,"published":773,"category":523,"image":778,"draft":527},"Source maps exposed: your app is publishing its original code","An exposed source map lets anyone read your app's original code, comments included. The 30-second check, and what actually matters if yours are public.","\u002Fblog\u002Fsource-maps-exposed-in-production\u002Fcard-800x500.png",{"path":780,"title":781,"description":782,"published":783,"category":523,"image":784,"draft":527},"\u002Fblog\u002Fsupabase-new-api-keys","Supabase's new API keys: which one is safe in your app?","Supabase replaced anon and service_role with publishable and secret keys. Which one belongs in your app, and which never does?","2026-08-12","\u002Fblog\u002Fsupabase-new-api-keys\u002Fcard-800x500.png",{"path":786,"title":787,"description":788,"published":789,"category":577,"image":790,"draft":527},"\u002Fblog\u002Fdoes-supabase-back-up-my-database","Does Supabase back up my database? It depends on your plan.","Does Supabase back up your database? Daily on paid plans, and not at all on the free one. How to check which you have, and what that copy cannot survive.","2026-08-11","\u002Fblog\u002Fdoes-supabase-back-up-my-database\u002Fcard-800x500.png",{"path":44,"title":792,"description":793,"published":794,"category":523,"image":795,"draft":527},"Supabase Row Level Security is on. Your table is still public.","Turning on Supabase Row Level Security does not protect a table. Your policies do, and the policy that fixed your broken app may let everyone in.","2026-08-10","\u002Fblog\u002Fsupabase-rls-on-but-table-still-public\u002Fcard-800x500.png",{"path":797,"title":798,"description":799,"published":794,"category":577,"image":800,"draft":527},"\u002Fblog\u002Fversion-history-is-not-a-backup","Version history is not a backup. It cannot undo a deleted table.","Lovable and Bolt keep version history for your code. Your database is a separate service, so rolling back to this morning does not bring your data back.","\u002Fblog\u002Fversion-history-is-not-a-backup\u002Fcard-800x500.png",{"path":802,"title":803,"description":804,"published":805,"category":577,"image":806,"draft":527},"\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database","Three ways to back up a Supabase database, and what each misses","The dashboard, pg_dump, and a managed service. What each one actually saves, what it quietly leaves out, and which one survives losing the account.","2026-08-09","\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database\u002Fcard-800x500.png",{"path":111,"title":808,"description":809,"published":805,"category":523,"image":810,"draft":527},"Which API keys are safe in your frontend, and which aren't","Your Supabase anon key is supposed to be public. Your service_role key is not, and it ignores every rule you set. Here is how to tell them apart.","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend\u002Fcard-800x500.png",1790150951364]