[{"data":1,"prerenderedAt":840},["ShallowReactive",2],{"blog-en-vite-and-next-public-env-vars":3,"blog-index-en":599},{"id":4,"title":5,"body":6,"category":551,"cover":552,"coverAlt":553,"description":554,"draft":555,"extension":556,"faq":557,"image":576,"keywords":577,"meta":587,"navigation":588,"ogTitle":589,"path":590,"published":591,"seo":592,"stem":593,"tldr":594,"updated":591,"__hash__":598},"blog_en\u002Fblog\u002Fvite-and-next-public-env-vars.md","Vite env variables exposed: VITE_ and NEXT_PUBLIC_ mean publish this",{"type":7,"value":8,"toc":537},"minimark",[9,25,40,43,48,54,72,86,93,99,105,121,127,133,137,140,151,154,160,164,167,318,324,333,337,340,360,371,375,378,398,408,413,421,425,428,435,456,464,472,476,519,522,529],[10,11,12,13,17,18,21,22,24],"p",{},"Somebody opened your Lovable app, pressed F12, and found a value in the code\nthat you are certain you put in a ",[14,15,16],"code",{},".env"," file. Or you asked the builder for a\nfeature, it wrote a line beginning ",[14,19,20],{},"VITE_",", and something you have read since\nsays that prefix is how keys leak. The file has ",[14,23,16],{}," in its name and every\ntutorial says never to share it. Vite env variables exposed to every visitor\nsounds like a bug in Vite.",[10,26,27,28,32,33,35,36,39],{},"Here is the part that guide after guide gets wrong: ",[29,30,31],"strong",{},"nothing failed to hide\nthat value."," ",[14,34,20],{}," and ",[14,37,38],{},"NEXT_PUBLIC_"," are an instruction to your build\ntool, and the instruction is to put the value into the app every visitor\ndownloads. The tool read the label and did what it said.",[10,41,42],{},"A build packs your app into a box, and every visitor gets a copy of the box.\nThe prefix is a label on one value that says pack this too. The rest of this\npost is about which values that label is right for, why the AI puts it on the\nwrong ones, and how to see what is in your own box.",[44,45,47],"h2",{"id":46},"are-vite-env-variables-exposed-to-visitors","Are Vite env variables exposed to visitors?",[10,49,50,51,53],{},"The ones that begin ",[14,52,20],{}," are, and that is what the prefix is for.",[10,55,56,57,59,60,63,64,67,68,71],{},"Vite, the build tool behind most Lovable and Bolt apps, reads your ",[14,58,16],{}," file\nevery time it builds. A variable named ",[14,61,62],{},"VITE_SUPABASE_URL"," is copied into the\nbundle, the compressed JavaScript file every visitor downloads, where your code\nreads it as ",[14,65,66],{},"import.meta.env.VITE_SUPABASE_URL",". A variable named\n",[14,69,70],{},"DB_PASSWORD",", with no prefix, comes back empty in the browser. Vite's own\ndocumentation says the prefixed values are bundled into your source code at\nbuild time and should not hold API keys.",[10,73,74,75,77,78,81,82,85],{},"Next.js, which v0 builds with, has the same rule under a different name:\n",[14,76,38],{},". Its documentation describes the value as inlined, a hard-coded\nstring written into the browser bundle when you build. Expo uses\n",[14,79,80],{},"EXPO_PUBLIC_"," and warns in the same words. Older Create React App projects use\n",[14,83,84],{},"REACT_APP_",". Each prefix tells its build tool the same thing: this one goes in\nthe box.",[44,87,89,90,92],{"id":88},"why-an-env-file-feels-private-and-is-not","Why an ",[14,91,16],{}," file feels private and is not",[10,94,95,96,98],{},"Because the habit of keeping a key in a ",[14,97,16],{}," file comes from servers, where\nit works.",[10,100,101,102,104],{},"On a server, the file and the code that reads it sit on a machine you control.\nA visitor gets an answer from that machine and never sees the file. Keeping the\nkey out of the code and in a file the server reads at start is good practice\nthere, and it is where every tutorial that says \"put it in ",[14,103,16],{},"\" was written.",[10,106,107,108,110,111,114,115,117,118,120],{},"Your ",[14,109,16],{}," file has two readers, and the tutorials are about one of them. The\nfirst is anyone who can see your project: a collaborator, a public GitHub\nrepository, the builder's own file view. A ",[14,112,113],{},".gitignore"," entry, which is a list\nof files git leaves out, keeps ",[14,116,16],{}," away from that reader. The second reader\nis the build, which opens the file every time you publish and copies out\nwhatever carries the prefix. ",[14,119,113],{}," says nothing to it.",[122,123],"diagram",{"alt":124,"caption":125,"src":126},"A .env file in the middle with three values, two of them tagged VITE_. To its left, a dashed wall labelled .gitignore stops the path to a stack of repository files. To its right, the two tagged values travel through a build step into a browser window with a person beside it, and the untagged value stays in the file.","One file, two readers. The .gitignore wall stops one of them. The build carries every prefixed value to the other.","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Ftwo-readers-one-file-1600x760.png",[10,128,129,130,132],{},"So \"my ",[14,131,16],{}," is gitignored\" is true, and it answers a different question. The\nfile stayed out of your repository. The values with a prefix went into the app\nanyway, because that is the route the prefix opens, and in a Lovable, Bolt or\nv0 project most of the code you have been editing runs in the browser, where\nthere is no server for the file to stay behind.",[44,134,136],{"id":135},"why-the-ai-reached-for-the-prefix","Why the AI reached for the prefix",[10,138,139],{},"Because it is how you make a value work in browser code, and the model has no\nidea what the value costs.",[10,141,142,143,146,147,150],{},"You asked for a map, or a chat feature that answers questions about your\nproduct. The code the builder wrote for it runs in the visitor's browser, and browser code reading\n",[14,144,145],{},"process.env.OPENAI_API_KEY"," gets nothing at all. The way to make the value\narrive is the prefix. The builder renames the variable ",[14,148,149],{},"VITE_OPENAI_API_KEY",",\nthe feature works in the preview, and no error is raised anywhere, because\nfrom the build tool's side nothing went wrong.",[10,152,153],{},"The prefix carries no judgement. It is the same instruction for a Google Maps\nkey, which is meant to be public once it is restricted, and for a Stripe\nsecret key, which can refund every customer you have. A person who knew the\nsecond one bills your card would stop. The model knows that the feature did\nnot work until the line was there, and then it did.",[10,155,156,157,159],{},"That is why this turns up on apps whose owners did everything they were told.\nThe value was moved out of the code, it was kept in ",[14,158,16],{},", the file was\ngitignored, and the app ships it anyway, because the one step that publishes\nit looks like the step that makes it work.",[44,161,163],{"id":162},"which-values-belong-behind-the-prefix","Which values belong behind the prefix",[10,165,166],{},"An address and a publishable key do. Anything that spends money or ignores your\ndatabase rules does not.",[168,169,170,192],"table",{},[171,172,173],"thead",{},[174,175,176,180,189],"tr",{},[177,178,179],"th",{},"Value",[177,181,182,183,185,186,188],{},"Behind ",[14,184,20],{}," or ",[14,187,38],{},"?",[177,190,191],{},"Why",[193,194,195,212,231,247,260,274,292,306],"tbody",{},[174,196,197,202,209],{},[198,199,200],"td",{},[14,201,62],{},[198,203,204],{},[205,206,208],"key-verdict",{"type":207},"safe","Belongs here",[198,210,211],{},"An address. It says which project your app talks to and nothing else.",[174,213,214,224,228],{},[198,215,216,219,220,223],{},[14,217,218],{},"VITE_SUPABASE_PUBLISHABLE_KEY",", or ",[14,221,222],{},"VITE_SUPABASE_ANON_KEY"," on an older project",[198,225,226],{},[205,227,208],{"type":207},[198,229,230],{},"Designed for the browser. Every request it makes is still filtered by Row Level Security, the rules on each table that decide row by row who may read what.",[174,232,233,240,244],{},[198,234,235,236,239],{},"A Stripe ",[14,237,238],{},"pk_live_"," key",[198,241,242],{},[205,243,208],{"type":207},[198,245,246],{},"Builds payment forms. Cannot charge, refund or read customers.",[174,248,249,252,257],{},[198,250,251],{},"A Google Maps key",[198,253,254],{},[205,255,256],{"type":207},"Belongs here, once restricted",[198,258,259],{},"Public by design. A referrer restriction in Google Cloud is what stops a stranger billing you with it.",[174,261,262,265,271],{},[198,263,264],{},"An OpenAI or Anthropic key",[198,266,267],{},[205,268,270],{"type":269},"danger","Never",[198,272,273],{},"There is no publishable variant. Whoever holds it spends your money.",[174,275,276,285,289],{},[198,277,278,279,185,282,239],{},"A Supabase ",[14,280,281],{},"service_role",[14,283,284],{},"sb_secret_",[198,286,287],{},[205,288,270],{"type":269},[198,290,291],{},"Bypasses Row Level Security and reads every row in every table.",[174,293,294,299,303],{},[198,295,235,296,239],{},[14,297,298],{},"sk_live_",[198,300,301],{},[205,302,270],{"type":269},[198,304,305],{},"Charges, refunds, payouts and every customer record.",[174,307,308,311,315],{},[198,309,310],{},"An AWS access key",[198,312,313],{},[205,314,270],{"type":269},[198,316,317],{},"Whatever that account can do, from anywhere.",[10,319,320,321,323],{},"If your app has ",[14,322,62],{}," and a publishable key beside it, that is the\npair Supabase intended for a browser, and our scan marks it as belonging there.\nThe address and the publishable key are the reason the prefix exists.",[10,325,326,327,332],{},"The test for anything else is whether you would mind the value printed on your\nhomepage. The prefix puts it one click further away than that, in a file\ninstead of on the page, and anyone who wants the file has it.\n",[328,329,331],"a",{"href":330},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","Telling the two families apart",",\nby prefix and on an older Supabase key by the role inside it, is its own\narticle.",[44,334,336],{"id":335},"what-30998-apps-shipped-behind-the-prefix","What 30,998 apps shipped behind the prefix",[10,338,339],{},"Mostly Google API keys. 52 apps shipped a key that spends money or reads\neverything.",[10,341,342,343,347,348,352,353,185,356,359],{},"In August 2026 we ran\n",[328,344,346],{"href":345},"\u002Fresearch\u002Fvibe-coded-app-security-2026","the same nine checks"," on 30,998 live\nvibe-coded apps. 1,332 of them, 4%, shipped something key-shaped in the code\nevery visitor downloads. 1,142 of those were Google API keys, which usually need\na ",[328,349,351],{"href":350},"\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend","restriction set in Google Cloud","\nand no rotation. 204 shipped a random-looking value sitting beside a name like\n",[14,354,355],{},"secret",[14,357,358],{},"password",", which may or may not be a real credential.",[10,361,362,363,365,366,370],{},"The expensive ones were rare. 33 apps shipped an OpenAI key, 9 an AWS access\nkey, 5 an Anthropic key, 3 a Stripe secret key and 3 a Supabase ",[14,364,281],{},"\nkey: 52 apps in all, since one of them carried two. So the value behind the\nprefix is usually a Google key, and the fix for that is a setting. The rare\ncase is where the damage is, and\n",[328,367,369],{"href":368},"\u002Fblog\u002Fopenai-api-key-exposed-in-frontend","what a leaked OpenAI key costs"," is\nthe post for that one.",[44,372,374],{"id":373},"how-to-check-what-your-own-app-is-shipping","How to check what your own app is shipping",[10,376,377],{},"Open the live app, press F12, and search every loaded file for the value.",[379,380,381,385,392,395],"ol",{},[382,383,384],"li",{},"Open your published app in a browser, at its real address. The builder's\npreview is a different build and can be a version behind.",[382,386,387,388,391],{},"Press F12 to open the developer tools, then choose the ",[29,389,390],{},"Sources"," tab.",[382,393,394],{},"Press Ctrl+Shift+F, or Cmd+Option+F on a Mac. That opens a search across\nevery file the page loaded.",[382,396,397],{},"Paste the first ten or so characters of the value you are worried about,\nand paste it nowhere else.",[10,399,400,401,404,405,407],{},"A hit means the value is in the box every visitor gets. Search for the value\nrather than the name: the build usually replaces\n",[14,402,403],{},"import.meta.env.VITE_OPENAI_API_KEY"," with the value itself, so a search for\n",[14,406,20],{}," can come back empty while every value behind it is there.",[122,409],{"alt":410,"caption":411,"src":412},"Two panes. On the left, a source file with a line reading import.meta.env.VITE_OPENAI_API_KEY among plain lines of code. On the right, after a build step, the same file compressed into dense noise, with the variable name gone and its value drawn as a highlighted bar under a magnifying glass.","The build replaces the name with the value. Searching the live app for VITE_ finds nothing; searching for the value finds it.","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fname-goes-value-stays-1600x640.png",[10,414,415,416,420],{},"If you would rather not go through your app value by value, our free scan reads\nyour live site from outside, all nine checks, in about 20 seconds with no\naccount. It names each key it finds by kind, says which ones belong in a\nbrowser, and prints \"Couldn't check\" for anything it could not answer rather\nthan a tick: ",[328,417,419],{"href":418},"\u002Fsecurity-scanner","scan your app",".",[44,422,424],{"id":423},"where-a-real-secret-goes-instead","Where a real secret goes instead",[10,426,427],{},"On a machine your visitors never download from. In a Supabase project that is\nan Edge Function, a small piece of server code Supabase runs for you; in a\nNext.js app it is a server route; in a Replit app it is the server half.",[10,429,430,431,434],{},"The shape is the same everywhere. Your browser code asks your function to do\nthe job. The function holds the key, makes the call to OpenAI or Stripe, and\nsends back the answer. The key stays on the machine and the visitor gets a\nresult. The ",[328,432,433],{"href":368},"OpenAI post"," draws it as\none object moving one box to the right, which is all the change is.",[10,436,437,438,441,442,444,445,448,449,452,453,420],{},"Two things tell you the builder did what you asked. The variable has lost its\nprefix, so it is ",[14,439,440],{},"OPENAI_API_KEY",", and it lives in the function's own secrets,\nset in the Supabase dashboard under Edge Functions, with nothing in the app's\n",[14,443,16],{},". And the file that reads it sits under ",[14,446,447],{},"supabase\u002Ffunctions\u002F"," or\n",[14,450,451],{},"app\u002Fapi\u002F",", somewhere the build never packs, instead of under ",[14,454,455],{},"src\u002F",[10,457,458,459,463],{},"One order matters, and it is easy to get backwards. If a secret key has already\nshipped behind a prefix, moving it does nothing for the copies already\ndownloaded. Rotate it at the provider first, then move the work.\n",[328,460,462],{"href":461},"\u002Fblog\u002Frotate-supabase-service-role-key","Whether to rotate first or close the leak first","\ndepends on whether the copy is already public, and once it has been in a\npublished bundle, it is.",[10,465,466,467,471],{},"For a Replit project the same split has its own name, Secrets, and\n",[328,468,470],{"href":469},"\u002Fblog\u002Freplit-secrets-explained","what the Secrets tool covers and what it does not","\nis its own post.",[44,473,475],{"id":474},"what-to-do-right-now","What to do right now",[477,478,479],"key-takeaways",{},[480,481,482,495,507,510,513,516],"ul",{},[382,483,484,485,487,488,487,490,35,492,494],{},"Search your project for ",[14,486,20],{},", ",[14,489,38],{},[14,491,80],{},[14,493,84],{},". Every match is a value your build publishes on purpose. Decide for each one whether it may be public.",[382,496,497,498,35,500,502,503,506],{},"Keep the address and the publishable key. ",[14,499,62],{},[14,501,218],{},", or the ",[14,504,505],{},"anon"," key on an older project, are the pair the prefix exists for.",[382,508,509],{},"A secret key behind a prefix is rotated at the provider first, then moved. Deleting the line does not recall a copy that was already downloaded.",[382,511,512],{},"Move the work that needed the key into an Edge Function or a server route, with the key in that function's secrets and no prefix on its name.",[382,514,515],{},"Restrict a Google key by referrer in Google Cloud. That one needs a setting and keeps its value.",[382,517,518],{},"After the next publish, search the live app for each value you moved.",[10,520,521],{},"Every publish packs a new box. The next feature you ask for is another chance\nfor a value to get the prefix, and nothing between the builder and the internet\nreads the bundle on the way out. A scan from last month read last month's\nbundle.",[10,523,524,528],{},[328,525,527],{"href":526},"\u002Fpricing","Reeve Monitor"," reads the bundle for you. It re-runs all nine checks\nevery hour on up to three apps, tells you when a result changes, watches\nuptime every 60 seconds, and sends a monthly report. A key that reaches the\nbundle with a Tuesday publish is in that hour's re-scan, whether or not you\nremembered to look. It is $12 a month at list, with seven days free before it\ncharges you; the pricing page is sometimes below the figure here and never\nabove it.",[10,530,531,532,536],{},"If you would rather work through this as a list, the\n",[328,533,535],{"href":534},"\u002Fchecklist","10-minute security checklist"," covers this and the other things\nworth switching off in a newly launched app.",{"title":538,"searchDepth":539,"depth":539,"links":540},"",3,[541,543,545,546,547,548,549,550],{"id":46,"depth":542,"text":47},2,{"id":88,"depth":542,"text":544},"Why an .env file feels private and is not",{"id":135,"depth":542,"text":136},{"id":162,"depth":542,"text":163},{"id":335,"depth":542,"text":336},{"id":373,"depth":542,"text":374},{"id":423,"depth":542,"text":424},{"id":474,"depth":542,"text":475},"Security basics","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fcover-1200x630.png","A .env file with three values, two of them labelled VITE_, and the same two values drawn again inside a browser window beside it.","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.",false,"md",[558,561,564,567,570,573],{"q":559,"a":560},"Are .env files secret?","From your repository, yes, if the file is listed in .gitignore. From your visitors, no. The build reads .env every time you publish and copies every value with a VITE_ or NEXT_PUBLIC_ prefix into the JavaScript your app ships. The file itself never leaves your machine; the values you labelled for the browser do.",{"q":562,"a":563},"Is VITE_SUPABASE_ANON_KEY safe to expose?","It is meant to be there. The anon key, called the publishable key on a newer project, is designed to sit in a browser. It only says which project a request belongs to, and every request it makes is filtered by your Row Level Security rules. That holds for exactly as long as those rules are on and correct, which is a separate thing to check.",{"q":565,"a":566},"Is NEXT_PUBLIC_ any different from VITE_?","Same rule, different build tool. Next.js writes the value of any NEXT_PUBLIC_ variable into the browser bundle as a hard-coded string when you build, and a variable without the prefix comes back empty in browser code. Expo does the same with EXPO_PUBLIC_, and older Create React App projects with REACT_APP_. Whichever tool built your app, the prefix means publish.",{"q":568,"a":569},"How do I check what is in my bundle?","Open the live app, press F12, choose Sources, and press Ctrl+Shift+F (Cmd+Option+F on a Mac) to search every file the page loaded. Paste the first few characters of the value. Search for the value rather than the variable name, because the build usually replaces the name with the value, so VITE_ can be absent while the key is there. Our free scan does the same read from outside in about 20 seconds.",{"q":571,"a":572},"Where should a secret key live in a Lovable or Bolt app?","In a Supabase Edge Function, with the key set in that function's secrets in the Supabase dashboard and no VITE_ prefix on its name. Your browser code calls the function, the function calls the provider with the key, and the key never reaches a visitor. If the key has already shipped, rotate it at the provider before you move it.",{"q":574,"a":575},"Does .gitignore protect my keys?","It keeps the .env file out of git, so nobody reading your repository sees it. It has no effect on the build, which reads the file directly and publishes every prefixed value. A gitignored .env with VITE_OPENAI_API_KEY in it still ships that key to every visitor.","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fcard-800x500.png",[578,579,580,581,582,583,584,585,586],"vite env variables exposed","VITE_ prefix security","next_public_ env variable secret","are .env variables safe in react","is vite_ safe","environment variables in frontend",".env file exposed in browser","expo_public env","hide api key in frontend",{},true,"Vite env variables exposed: the prefix means publish this","\u002Fblog\u002Fvite-and-next-public-env-vars","2026-09-18",{"title":5,"description":554},"blog\u002Fvite-and-next-public-env-vars",[595,596,597],"Vite env variables exposed in your app are there because the prefix asked for it. A variable named VITE_ or NEXT_PUBLIC_ is copied into the JavaScript every visitor downloads, and keeping it in a .env file does nothing to stop that.","The prefix is the right label for an address or a publishable key and the wrong one for anything that spends money or ignores your database rules. The build cannot tell those apart, and neither can the AI that wrote the line.","Of 30,998 live vibe-coded apps we scanned, 1,332 shipped something key-shaped. 1,142 of those were Google API keys, which usually need a restriction and no rotation. 52 shipped a key that spends money or reads everything.","6_a_xkidyvwC9eKqabCjgk7tNCfcR3EgXqLaYrFw9Jg",[600,607,613,619,625,631,632,638,644,650,656,662,668,674,680,686,691,697,703,709,715,720,726,732,737,743,749,755,761,767,773,779,785,790,796,802,807,813,819,825,830,836],{"path":601,"title":602,"description":603,"published":604,"category":605,"image":606,"draft":555},"\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":608,"title":609,"description":610,"published":611,"category":551,"image":612,"draft":555},"\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":614,"title":615,"description":616,"published":617,"category":551,"image":618,"draft":555},"\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":620,"title":621,"description":622,"published":623,"category":551,"image":624,"draft":555},"\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":626,"title":627,"description":628,"published":629,"category":551,"image":630,"draft":555},"\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":590,"title":5,"description":554,"published":591,"category":551,"image":576,"draft":555},{"path":633,"title":634,"description":635,"published":636,"category":551,"image":637,"draft":555},"\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":639,"title":640,"description":641,"published":642,"category":551,"image":643,"draft":555},"\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":645,"title":646,"description":647,"published":648,"category":551,"image":649,"draft":555},"\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":651,"title":652,"description":653,"published":654,"category":605,"image":655,"draft":555},"\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":657,"title":658,"description":659,"published":660,"category":605,"image":661,"draft":555},"\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":663,"title":664,"description":665,"published":666,"category":605,"image":667,"draft":555},"\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":669,"title":670,"description":671,"published":672,"category":551,"image":673,"draft":555},"\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":675,"title":676,"description":677,"published":678,"category":551,"image":679,"draft":555},"\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":681,"title":682,"description":683,"published":684,"category":551,"image":685,"draft":555},"\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":461,"title":687,"description":688,"published":689,"category":551,"image":690,"draft":555},"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":692,"title":693,"description":694,"published":695,"category":551,"image":696,"draft":555},"\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":698,"title":699,"description":700,"published":701,"category":551,"image":702,"draft":555},"\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":704,"title":705,"description":706,"published":707,"category":551,"image":708,"draft":555},"\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":710,"title":711,"description":712,"published":713,"category":551,"image":714,"draft":555},"\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":368,"title":716,"description":717,"published":718,"category":551,"image":719,"draft":555},"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":721,"title":722,"description":723,"published":724,"category":551,"image":725,"draft":555},"\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":727,"title":728,"description":729,"published":730,"category":605,"image":731,"draft":555},"\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":469,"title":733,"description":734,"published":735,"category":551,"image":736,"draft":555},"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":738,"title":739,"description":740,"published":741,"category":605,"image":742,"draft":555},"\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":744,"title":745,"description":746,"published":747,"category":551,"image":748,"draft":555},"\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":750,"title":751,"description":752,"published":753,"category":551,"image":754,"draft":555},"\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":756,"title":757,"description":758,"published":759,"category":551,"image":760,"draft":555},"\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":762,"title":763,"description":764,"published":765,"category":551,"image":766,"draft":555},"\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":768,"title":769,"description":770,"published":771,"category":605,"image":772,"draft":555},"\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":774,"title":775,"description":776,"published":777,"category":605,"image":778,"draft":555},"\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":780,"title":781,"description":782,"published":783,"category":551,"image":784,"draft":555},"\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":350,"title":786,"description":787,"published":788,"category":551,"image":789,"draft":555},"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":791,"title":792,"description":793,"published":794,"category":605,"image":795,"draft":555},"\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":797,"title":798,"description":799,"published":800,"category":551,"image":801,"draft":555},"\u002Fblog\u002Fcan-anyone-read-your-supabase-database","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":803,"title":804,"description":805,"published":800,"category":551,"image":806,"draft":555},"\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":808,"title":809,"description":810,"published":811,"category":551,"image":812,"draft":555},"\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":814,"title":815,"description":816,"published":817,"category":605,"image":818,"draft":555},"\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":820,"title":821,"description":822,"published":823,"category":551,"image":824,"draft":555},"\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":826,"title":827,"description":828,"published":823,"category":605,"image":829,"draft":555},"\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":831,"title":832,"description":833,"published":834,"category":605,"image":835,"draft":555},"\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":330,"title":837,"description":838,"published":834,"category":551,"image":839,"draft":555},"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]