[{"data":1,"prerenderedAt":802},["ShallowReactive",2],{"blog-en-enable-rls-on-every-supabase-table":3,"blog-index-en":563},{"id":4,"title":5,"body":6,"category":519,"cover":520,"coverAlt":521,"description":522,"draft":523,"extension":524,"faq":525,"image":541,"keywords":542,"meta":551,"navigation":552,"ogTitle":553,"path":554,"published":555,"seo":556,"stem":557,"tldr":558,"updated":555,"__hash__":562},"blog_en\u002Fblog\u002Fenable-rls-on-every-supabase-table.md","Enable Row Level Security on every Supabase table, then prove it",{"type":7,"value":8,"toc":507},"minimark",[9,13,21,26,29,32,39,45,48,52,55,58,69,80,86,89,95,98,114,118,121,127,133,159,165,171,174,180,186,193,276,288,292,295,298,303,306,309,315,319,322,325,328,334,344,347,351,354,360,367,372,393,406,413,417,420,423,435,438,441,453,457,494],[10,11,12],"p",{},"You have been told to turn on Row Level Security, or your AI builder mentioned\nit in passing while fixing something else. Your Supabase project has somewhere\nbetween four and forty tables in it, and you do not know which of them are\ncovered.",[10,14,15,16,20],{},"The instruction you will find everywhere is one line of SQL per table, and it is\nright as far as it goes. What almost every guide stops short of is the step\nafter it. ",[17,18,19],"strong",{},"A policy that exists is not a policy that works",", and nothing in\nyour dashboard will show you the difference. So enabling Row Level Security in\nSupabase is three pieces of work rather than one: switch it on across every\ntable, write the two or three policies that put your app back together, then ask\nyour own database the question a stranger would ask it.",[22,23,25],"h2",{"id":24},"what-does-enabling-row-level-security-actually-do","What does enabling Row Level Security actually do?",[10,27,28],{},"It makes Postgres consult your rules before it hands over a row. With the\nsetting off there are no rules to consult, so the answer to every request is\neverything.",[10,30,31],{},"Picture a librarian who fetches whatever you ask for. Row Level Security is the\ninstruction to check a note about you before filling the trolley. The note is\nyour policy, and it might say that this person may take the books they wrote, or\nit might say that anybody may take anything. With the instruction in place and\nno note written yet, the librarian comes back with an empty trolley and offers\nno explanation.",[10,33,34,35,38],{},"That last part is the piece people trip over, and it decides what a test looks\nlike later. ",[17,36,37],{},"Row Level Security filters rows. It does not refuse requests."," A\ntable you are not allowed to read answers with an empty list and a success code,\nnot with an error or a login prompt. Your app is never told that it was turned\ndown. It receives nothing, and it renders a blank screen.",[40,41],"diagram",{"alt":42,"caption":43,"src":44},"The same request drawn three times. With the security switch off, four lit rows come back. With the switch on and no policy, an empty dashed slot comes back. With the switch on and an ownership policy, one lit row comes back. All three returns are marked 200.","One request, three settings, and the same success code every time. Row level security changes what comes back, never whether the request succeeded.","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Fsame-request-three-answers-1600x780.png",[10,46,47],{},"So switching the setting on across a project that has no policies in it yet does\nnot lock strangers out of your data. It locks everyone out, your own app\nincluded, until you say who is allowed to see what.",[22,49,51],{"id":50},"how-do-i-enable-rls-on-every-supabase-table-at-once","How do I enable RLS on every Supabase table at once?",[10,53,54],{},"One loop, run once in the SQL Editor. It walks every table in your public schema\nand turns the setting on wherever it is off.",[10,56,57],{},"Start by seeing where you stand. This lists your tables and says whether each\none currently has it:",[59,60,65],"pre",{"className":61,"code":63,"language":64},[62],"language-text","select tablename, rowsecurity\nfrom pg_tables\nwhere schemaname = 'public'\norder by tablename;\n","text",[66,67,63],"code",{"__ignoreMap":68},"",[10,70,71,72,75,76,79],{},"Every row where ",[66,73,74],{},"rowsecurity"," reads ",[66,77,78],{},"false"," is a table handing its contents to\nanybody holding the publishable key that ships in your app. If that list is\nshort, do them one at a time and watch what breaks:",[59,81,84],{"className":82,"code":83,"language":64},[62],"alter table public.orders enable row level security;\n",[66,85,83],{"__ignoreMap":68},[10,87,88],{},"If it is not short, this covers the lot:",[59,90,93],{"className":91,"code":92,"language":64},[62],"do $$\ndeclare t record;\nbegin\n  for t in\n    select tablename from pg_tables where schemaname = 'public'\n  loop\n    execute format(\n      'alter table public.%I enable row level security', t.tablename\n    );\n  end loop;\nend $$;\n",[66,94,92],{"__ignoreMap":68},[10,96,97],{},"Run that and your app goes blank. Supabase says so plainly in its own\ndocumentation: data becomes inaccessible through the API using a publishable key\nuntil policies are defined. It is the setting doing its job, and it is why the\nnext section is the one to have open before you press run.",[99,100,102],"callout",{"type":101},"warn",[10,103,104,107,108,113],{},[17,105,106],{},"The tables your builder made are the ones that were open."," A table created by\nclicking through the Supabase Table Editor gets row level security switched on\nfor you. A table created by running SQL does not, and running SQL is how\nLovable, Bolt, Cursor and v0 make tables on your behalf. The tables you never\nwatched being made are the ones this loop is for, and\n",[109,110,112],"a",{"href":111},"\u002Fblog\u002Fsupabase-rls-disabled-in-public","what the \"RLS disabled in public\" warning does and does not catch","\ncovers why your dashboard may have stayed quiet about them.",[22,115,117],{"id":116},"the-three-policies-you-actually-need","The three policies you actually need",[10,119,120],{},"Almost every table in an app like yours is one of three shapes: rows that belong\nto one person, rows anyone may read, and rows your app writes on a visitor's\nbehalf. Here is each of them, ready to paste and rename.",[10,122,123,126],{},[17,124,125],{},"Rows that belong to one person."," Orders, messages, saved items, anything with\nan owner.",[59,128,131],{"className":129,"code":130,"language":64},[62],"create policy \"read own orders\"\non public.orders for select\nto authenticated\nusing ( (select auth.uid()) = user_id );\n",[66,132,130],{"__ignoreMap":68},[10,134,135,138,139,142,143,146,147,150,151,154,155,158],{},[66,136,137],{},"auth.uid()"," is the id of whoever is signed in on that request. ",[66,140,141],{},"user_id"," is\nwhatever column on your table records the owner, so check the name before you\nrun it: builders also write ",[66,144,145],{},"owner_id",", ",[66,148,149],{},"profile_id"," and ",[66,152,153],{},"created_by",". The\n",[66,156,157],{},"to authenticated"," line means the policy is never even considered for a\nsigned-out visitor, which is what keeps the table closed to the public.",[10,160,161,164],{},[17,162,163],{},"Rows anyone may read."," A product catalogue, published articles, a map of\nvenues.",[59,166,169],{"className":167,"code":168,"language":64},[62],"create policy \"anyone may read products\"\non public.products for select\nto anon, authenticated\nusing ( true );\n",[66,170,168],{"__ignoreMap":68},[10,172,173],{},"Write this one deliberately or not at all, because it is also the policy an AI\nbuilder reaches for the moment you ask it to fix an empty screen. The question\nto settle first: could this table be a page on your site, exactly as it stands,\nwith nothing taken out? A no means it wants the ownership policy above instead.",[10,175,176,179],{},[17,177,178],{},"Rows your app writes."," Reading and writing are separate permissions in\nPostgres, so a table your app saves to needs a second policy, and this one\nchecks the row on its way in rather than on its way out.",[59,181,184],{"className":182,"code":183,"language":64},[62],"create policy \"insert own orders\"\non public.orders for insert\nto authenticated\nwith check ( (select auth.uid()) = user_id );\n",[66,185,183],{"__ignoreMap":68},[10,187,188,189,192],{},"Which clause goes where is the part that catches people, and the ",[66,190,191],{},"update"," row\ncarries a requirement that has nothing obvious about it:",[194,195,196,212],"table",{},[197,198,199],"thead",{},[200,201,202,206,209],"tr",{},[203,204,205],"th",{},"Operation",[203,207,208],{},"Clause",[203,210,211],{},"Also needs",[213,214,215,230,244,263],"tbody",{},[200,216,217,223,228],{},[218,219,220],"td",{},[66,221,222],{},"select",[218,224,225],{},[66,226,227],{},"using",[218,229],{},[200,231,232,237,242],{},[218,233,234],{},[66,235,236],{},"insert",[218,238,239],{},[66,240,241],{},"with check",[218,243],{},[200,245,246,250,257],{},[218,247,248],{},[66,249,191],{},[218,251,252,253,150,255],{},"both ",[66,254,227],{},[66,256,241],{},[218,258,259,260,262],{},"a ",[66,261,222],{}," policy on the same table",[200,264,265,270,274],{},[218,266,267],{},[66,268,269],{},"delete",[218,271,272],{},[66,273,227],{},[218,275],{},[10,277,278,279,282,283,287],{},"Supabase's documentation is explicit about that last one: without a\ncorresponding select policy, an update will not work as expected. And if the\nmessage ",[66,280,281],{},"new row violates row-level security policy"," is what sent you looking in\nthe first place, the gap between those two clauses is\n",[109,284,286],{"href":285},"\u002Fblog\u002Fnew-row-violates-row-level-security-policy","the whole of that error",".",[22,289,291],{"id":290},"why-every-example-writes-select-authuid-instead-of-authuid","Why every example writes (select auth.uid()) instead of auth.uid()",[10,293,294],{},"Because the brackets make Postgres work the value out once for the whole query\nrather than once for every row it examines.",[10,296,297],{},"The wrapped version becomes what Postgres calls an initPlan, which it runs a\nsingle time and then reuses for the rest of the statement. Without the brackets\nthe function is called again on row one, row two, row three, and on down a table\nthat might hold a hundred thousand of them. Supabase's own Performance Advisor\nreports the unwrapped version under a rule named Auth RLS Initialization Plan,\nand it is one of the commonest entries people find sitting in there.",[40,299],{"alt":300,"caption":301,"src":302},"Two panels over the same eight table rows. In the left panel a function mark sits beside every row with its own connector, eight in total. In the right panel a single function mark at the top fans out to all eight rows at once.","The brackets are the whole difference. On the left the function runs once per row; on the right it runs once and the answer is reused.","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Fonce-per-query-1600x620.png",[10,304,305],{},"One caveat, and it is Supabase's own: this works because the answer does not\nchange from row to row. A function whose result genuinely depends on the row in\nfront of it cannot be hoisted out of the loop, so leave that one unwrapped.",[10,307,308],{},"While you are here, add an index on the column your policies filter on. The\npolicy becomes a condition on every read of that table, so on a table with a lot\nof rows in it an unindexed column shows up in your response times:",[59,310,313],{"className":311,"code":312,"language":64},[62],"create index orders_user_id_idx on public.orders (user_id);\n",[66,314,312],{"__ignoreMap":68},[22,316,318],{"id":317},"testing-a-policy-without-making-a-fake-account","Testing a policy without making a fake account",[10,320,321],{},"The Supabase SQL Editor can run a query as though a particular visitor sent it,\nwhich covers the anonymous case completely.",[10,323,324],{},"The editor carries a control for the role a query should run as. Set it to the\nanonymous role, then run an ordinary select against the table you just changed.\nWhat comes back is what a signed-out stranger gets. For a signed-in visitor, pick the id of a\nuser you already have rather than making a new one; any row in your own table is\nenough to test against.",[10,326,327],{},"If you would rather type it than click it, the same thing in SQL:",[59,329,332],{"className":330,"code":331,"language":64},[62],"begin;\nset local role anon;\nselect * from public.orders;\nrollback;\n",[66,333,331],{"__ignoreMap":68},[10,335,336,337,150,340,343],{},"The ",[66,338,339],{},"begin",[66,341,342],{},"rollback"," are there so the role change lasts for that block and\nno longer, which matters if you are working through several tables in one sitting.",[10,345,346],{},"What this tells you is what your policies do inside the database. What it cannot\ntell you is what your project hands to the internet, because the request your\nvisitors actually make does not start in the SQL Editor. It starts in a browser,\ncarries a publishable key, and arrives through your project's public address.",[22,348,350],{"id":349},"how-do-i-test-supabase-rls-from-outside-my-app","How do I test Supabase RLS from outside my app?",[10,352,353],{},"Send the request a stranger would send. You need two values and both of them are\nalready sitting in the code your site serves to every visitor: your project URL\nand your publishable key.",[59,355,358],{"className":356,"code":357,"language":64},[62],"curl \"https:\u002F\u002FYOUR-PROJECT.supabase.co\u002Frest\u002Fv1\u002Forders?select=*&limit=1\" \\\n  -H \"apikey: YOUR_PUBLISHABLE_KEY\" \\\n  -H \"Authorization: Bearer YOUR_PUBLISHABLE_KEY\"\n",[66,359,357],{"__ignoreMap":68},[10,361,362,363,366],{},"Swap in one table name at a time and read what comes back. An empty list, ",[66,364,365],{},"[]",",\nmeans the policy held for a signed-out visitor. A row means anyone holding a key\nthat ships in your app can read that table. An error mentioning the key itself\nmeans you copied the wrong value, which is worth ruling out before you conclude\nanything.",[40,368],{"alt":369,"caption":370,"src":371},"A dashed boundary around a database table. Inside it a magnifier sits beside the table with a short arrow. Outside it a person sends an arrow that carries a key through a gap in the boundary, the path slash rest slash v1 written beside the gap, and reaches the same table.","The editor test never leaves the building. The request from outside arrives through the same public address your visitors use, carrying the same key your app ships.","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Ftwo-tests-one-table-1600x640.png",[10,373,374,375,378,379,382,383,387,388,392],{},"On a newer Supabase project that key begins ",[66,376,377],{},"sb_publishable_",", and on an older\none it is the ",[66,380,381],{},"anon"," key. Either is safe to use this way and safe to have in your\napp, which is the point of it;\n",[109,384,386],{"href":385},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","which API keys belong in a frontend","\ncovers the pair that is not, and\n",[109,389,391],{"href":390},"\u002Fblog\u002Fwhere-to-find-supabase-api-keys","where to find them in the dashboard"," is\nthe four values on that settings page.",[10,394,395,396,400,401,405],{},"This is the test that matches reality, and running it at scale is how we know\nhow common the gap is. Between 12 and 14 August 2026 we ran nine external checks\nover 30,998 live apps published from Lovable, Base44, Replit, v0 and Bolt. Of\nthe 3,680 Supabase-backed apps where the check could complete, 2,096 answered an\nanonymous request with rows from at least one table. That is 57%, and it is a\nshare of the apps that gave us a straight answer rather than of everything we\nscanned. The ",[109,397,399],{"href":398},"\u002Fresearch\u002Fvibe-coded-app-security-2026","full dataset is published",",\nand ",[109,402,404],{"href":403},"\u002Fblog\u002Fcan-anyone-read-your-supabase-database","what that 57% is a share of","\nwalks through the counting.",[10,407,408,409,287],{},"Doing this by hand is fine for four tables and tedious for forty. Our free scan\nsends that request for you, works out which tables exist without you naming\nthem, and tells you which ones answered, alongside eight other checks it makes\nfrom outside. It reads your live site the way any visitor can, takes about 20\nseconds and needs no account: ",[109,410,412],{"href":411},"\u002Fsecurity-scanner","scan your app",[22,414,416],{"id":415},"before-you-rewrite-policies-on-a-live-table","Before you rewrite policies on a live table",[10,418,419],{},"Take a copy of your database first. You are about to change permissions across\nevery table you have, using the same tools that produced the problem.",[10,421,422],{},"Two different risks are worth separating, because only one of them is about the\nwork you are doing today. If a table was readable and writable by anyone, then\ntightening it now does nothing about what already happened, and that version\ntends to surface as a support message about data that changed on its own. The\nother risk is the migration itself. A policy dropped and recreated slightly\nwrong is an ordinary Tuesday, and the way back is a copy of how things looked\nan hour before.",[10,424,425,426,430,431,287],{},"On a paid Supabase plan there is last night's copy waiting in the console. On\nthe free plan there is nothing to fall back to at all, because\n",[109,427,429],{"href":428},"\u002Fblog\u002Fdoes-supabase-back-up-my-database","Supabase takes no automatic backups on the free tier",".\nIf that is you,\n",[109,432,434],{"href":433},"\u002Fblog\u002Fback-up-supabase-free-tier","take one before you start",[10,436,437],{},"Reeve Care is the version of that you do not have to remember. Your Supabase\ndatabase is copied on a schedule, kept outside your Supabase account, encrypted,\nand read back to confirm it restores before the date on your dashboard moves.\nUploaded files travel with it once you connect a Storage key, and that key is\nasked for separately because Supabase issues no read-only key for files: the one\nthat copies your uploads can also write, where the one that copies your database\ncannot. Connecting it is optional and your database is backed up either way.\nBackups are Supabase only, so if your data lives elsewhere we say so rather than\nsell you a subscription watching an empty box.",[10,439,440],{},"Restoring is the part that matters on a day like this one. Care copies the\ncurrent state of your database before it replays the version you picked, so\npressing the button has an undo of its own.",[10,442,443,444,448,449,287],{},"The other half is the check you just ran by hand. A policy that loosens during\nsome later migration is not a thing anyone finds by looking, so Care re-runs the\nsame anonymous request on a schedule and tells you when a table starts answering\nthat was quiet last week. Uptime monitoring and a monthly report sit on the same\nsubscription. None of that writes your policies for you, and no backup makes an\nopen table closed. What it changes is how much a bad migration costs you.\n",[109,445,447],{"href":446},"\u002Fsupabase-backups","What Reeve backs up on Supabase, how often, and what a restore does","\nwalks the whole cycle, and the plans are on the ",[109,450,452],{"href":451},"\u002Fpricing","pricing page",[22,454,456],{"id":455},"the-order-to-work-in","The order to work in",[458,459,460],"key-takeaways",{},[461,462,463,471,474,481,491],"ul",{},[464,465,466,467,470],"li",{},"List your tables with ",[66,468,469],{},"select tablename, rowsecurity from pg_tables where schemaname = 'public'"," and see how many are open before you change anything.",[464,472,473],{},"Take a backup, then enable row level security on every table in the public schema. Expect your app to go blank; that is the setting working.",[464,475,476,477,480],{},"Give each table one of the three policies. Ownership is the default; ",[66,478,479],{},"USING (true)"," is a decision you make table by table, not a way to get the screens back.",[464,482,483,484,487,488,490],{},"Write ",[66,485,486],{},"(select auth.uid())"," rather than ",[66,489,137],{},", and index the column your policies filter on.",[464,492,493],{},"Test each table from outside with no login. An empty list is the pass. A row is a table anyone can read, whatever your dashboard says about it.",[10,495,496,497,501,502,506],{},"Start with the table that would embarrass you most as a public page, and work\ndown from there. The ",[109,498,500],{"href":499},"\u002Fchecklist","10-minute security checklist"," covers this\nalongside the rest of what is worth confirming in a newly launched app, and the\n",[109,503,505],{"href":504},"\u002Fis-your-supabase-app-safe","Supabase safety guide"," goes through what else tends\nto get left open.",{"title":68,"searchDepth":508,"depth":508,"links":509},3,[510,512,513,514,515,516,517,518],{"id":24,"depth":511,"text":25},2,{"id":50,"depth":511,"text":51},{"id":116,"depth":511,"text":117},{"id":290,"depth":511,"text":291},{"id":317,"depth":511,"text":318},{"id":349,"depth":511,"text":350},{"id":415,"depth":511,"text":416},{"id":455,"depth":511,"text":456},"Security basics","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Fcover-1200x630.png","A list of database tables in a panel, each with a security switch beside it, and most of the switches still off.","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.",false,"md",[526,529,532,535,538],{"q":527,"a":528},"What happens if I enable RLS and write no policy?","The table stops answering, including for your own app. With the setting on, Postgres consults your policies before handing over a row, and with no policies there is nothing that can say yes, so requests come back empty. Supabase documents this directly: data becomes inaccessible through the API using a publishable key until policies are defined. Nothing is deleted and nothing is broken. Write a policy for the rows your app is meant to show and the screens come back.",{"q":530,"a":531},"Does Row Level Security slow my queries down?","It can, and the two fixes are small. Write `(select auth.uid())` in the policy condition, with the brackets, which lets Postgres work the value out once for the whole query and then reuse it for every row. Supabase flags the unwrapped version in its own Performance Advisor, under a rule called Auth RLS Initialization Plan. Then add an index on the column the policy filters on, usually `user_id`. On a table holding a few thousand rows you are unlikely to notice either way. On a large one, both of them matter.",{"q":533,"a":534},"Do I need RLS if my table has no personal data?","You still want it switched on, and the policy can be the permissive one. A table with row level security off is readable by anyone holding the publishable key that ships in your app, which is fine for a product catalogue and much less fine for anything you would not publish as a page. Switching it on and writing a read policy of `USING (true)` gives you that same public access on purpose, and it means the table reads as decided rather than as forgotten the next time somebody goes through the list.",{"q":536,"a":537},"Why did my realtime subscription stop working after I enabled RLS?","Because Realtime consults the same policies before it sends a change to a subscriber. A table with row level security on and no select policy for that visitor delivers no rows to a query and no changes to a subscription, for exactly the same reason. Add the select policy that subscriber needs and the stream resumes. If you are using Realtime broadcast or presence rather than database changes, those are authorized separately, through policies written on the `realtime.messages` table.",{"q":539,"a":540},"How do I test a policy without creating a fake user?","Two ways, and neither one needs a new account. The Supabase SQL Editor can run a query as though a particular role sent it, which covers the anonymous case completely: whatever comes back is what a stranger gets. For the signed-in case you need a user id to stand in for, and any row already in your own table will do. The second way is a request from outside carrying your publishable key, which tests the whole path rather than only the policy.","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Fcard-800x500.png",[543,544,545,546,547,548,549,550],"enable row level security supabase","supabase enable rls","row level security policies supabase","supabase rls policy example","supabase rls select auth.uid","test supabase rls","supabase rls best practices","auth.uid() policy",{},true,"Enable Row Level Security on every Supabase table","\u002Fblog\u002Fenable-rls-on-every-supabase-table","2026-09-10",{"title":5,"description":522},"blog\u002Fenable-rls-on-every-supabase-table",[559,560,561],"Enabling Row Level Security in Supabase with no policy locks a table completely, and writing a policy without enabling the setting does nothing at all. Every table needs both.","Three policy shapes cover almost everything an AI builder makes: rows that belong to one person, rows anyone may read, and rows your app writes on a visitor's behalf.","Then check from outside your app with no login, because that is the request a stranger makes, and it is the only one that tells you what your policies do rather than what they say.","PpNkPhakRrLsbYaxTsAUQ7I9tWHKyRA1IOhV1d_aZGI",[564,571,577,583,589,595,601,607,613,619,625,631,637,643,644,649,655,661,667,673,679,685,691,697,703,708,714,719,724,730,736,742,748,754,760,765,770,776,781,787,792,798],{"path":565,"title":566,"description":567,"published":568,"category":569,"image":570,"draft":523},"\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":572,"title":573,"description":574,"published":575,"category":519,"image":576,"draft":523},"\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":578,"title":579,"description":580,"published":581,"category":519,"image":582,"draft":523},"\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":584,"title":585,"description":586,"published":587,"category":519,"image":588,"draft":523},"\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":590,"title":591,"description":592,"published":593,"category":519,"image":594,"draft":523},"\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":596,"title":597,"description":598,"published":599,"category":519,"image":600,"draft":523},"\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":602,"title":603,"description":604,"published":605,"category":519,"image":606,"draft":523},"\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":608,"title":609,"description":610,"published":611,"category":519,"image":612,"draft":523},"\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":614,"title":615,"description":616,"published":617,"category":519,"image":618,"draft":523},"\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":620,"title":621,"description":622,"published":623,"category":569,"image":624,"draft":523},"\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":626,"title":627,"description":628,"published":629,"category":569,"image":630,"draft":523},"\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":632,"title":633,"description":634,"published":635,"category":569,"image":636,"draft":523},"\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":638,"title":639,"description":640,"published":641,"category":519,"image":642,"draft":523},"\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":554,"title":5,"description":522,"published":555,"category":519,"image":541,"draft":523},{"path":111,"title":645,"description":646,"published":647,"category":519,"image":648,"draft":523},"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":650,"title":651,"description":652,"published":653,"category":519,"image":654,"draft":523},"\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":656,"title":657,"description":658,"published":659,"category":519,"image":660,"draft":523},"\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":662,"title":663,"description":664,"published":665,"category":519,"image":666,"draft":523},"\u002Fblog\u002Fsupabase-security-checker","Supabase security checker: run the five checks yourself","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.","2026-09-06","\u002Fblog\u002Fsupabase-security-checker\u002Fcard-800x500.png",{"path":668,"title":669,"description":670,"published":671,"category":519,"image":672,"draft":523},"\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":674,"title":675,"description":676,"published":677,"category":519,"image":678,"draft":523},"\u002Fblog\u002Fmissing-security-headers","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":680,"title":681,"description":682,"published":683,"category":519,"image":684,"draft":523},"\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":686,"title":687,"description":688,"published":689,"category":519,"image":690,"draft":523},"\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":692,"title":693,"description":694,"published":695,"category":569,"image":696,"draft":523},"\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":698,"title":699,"description":700,"published":701,"category":519,"image":702,"draft":523},"\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":433,"title":704,"description":705,"published":706,"category":569,"image":707,"draft":523},"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":709,"title":710,"description":711,"published":712,"category":519,"image":713,"draft":523},"\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":390,"title":715,"description":716,"published":717,"category":519,"image":718,"draft":523},"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":285,"title":720,"description":721,"published":722,"category":519,"image":723,"draft":523},"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":725,"title":726,"description":727,"published":728,"category":519,"image":729,"draft":523},"\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":731,"title":732,"description":733,"published":734,"category":569,"image":735,"draft":523},"\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":737,"title":738,"description":739,"published":740,"category":569,"image":741,"draft":523},"\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":743,"title":744,"description":745,"published":746,"category":519,"image":747,"draft":523},"\u002Fblog\u002Fsupabase-storage-bucket-public","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":749,"title":750,"description":751,"published":752,"category":519,"image":753,"draft":523},"\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":755,"title":756,"description":757,"published":758,"category":569,"image":759,"draft":523},"\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":403,"title":761,"description":762,"published":763,"category":519,"image":764,"draft":523},"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":766,"title":767,"description":768,"published":763,"category":519,"image":769,"draft":523},"\u002Fblog\u002Fsource-maps-exposed-in-production","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":771,"title":772,"description":773,"published":774,"category":519,"image":775,"draft":523},"\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":428,"title":777,"description":778,"published":779,"category":569,"image":780,"draft":523},"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":782,"title":783,"description":784,"published":785,"category":519,"image":786,"draft":523},"\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","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":788,"title":789,"description":790,"published":785,"category":569,"image":791,"draft":523},"\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":793,"title":794,"description":795,"published":796,"category":569,"image":797,"draft":523},"\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":385,"title":799,"description":800,"published":796,"category":519,"image":801,"draft":523},"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]