[{"data":1,"prerenderedAt":714},["ShallowReactive",2],{"blog-en-openai-api-key-exposed-in-frontend":3,"blog-index-en":472},{"id":4,"title":5,"body":6,"category":429,"cover":430,"coverAlt":431,"description":432,"draft":433,"extension":434,"faq":435,"image":451,"keywords":452,"meta":458,"navigation":459,"ogTitle":460,"path":461,"published":462,"seo":463,"stem":464,"tldr":465,"updated":470,"__hash__":471},"blog_en\u002Fblog\u002Fopenai-api-key-exposed-in-frontend.md","Your OpenAI API key is exposed in your frontend. Rotate it.",{"type":7,"value":8,"toc":415},"minimark",[9,18,26,35,40,43,54,57,60,64,67,82,96,99,103,106,114,117,123,131,134,140,151,154,158,161,164,167,170,174,177,180,185,188,214,217,220,224,242,245,248,253,348,351,368,372,398],[10,11,12,13,17],"p",{},"Open your app in a browser, view the page source, and search it for ",[14,15,16],"code",{},"sk-proj-",".\nIf a long string comes back, your OpenAI API key is exposed in your frontend, and\nevery visitor you have ever had could have copied it.",[10,19,20,21,25],{},"Here is the part that most advice on this gets wrong: ",[22,23,24],"strong",{},"an OpenAI key is not a\nGoogle key with a different prefix, and nothing you can set in a dashboard makes\nit safe where it is."," A Google Maps key belongs in your page, and one free\nsetting protects it. An OpenAI key has no equivalent setting anywhere, which is\nwhy the only honest first instruction is to rotate it.",[10,27,28,29,34],{},"Between 12 and 14 August 2026 we ran nine external checks over 30,998 live apps\nbuilt with Lovable, Bolt, v0, Replit and Base44. An OpenAI key turned up in 33 of\nthem. All 33 came back graded D or F, because a single critical finding caps the\ngrade no matter what else the app got right. The full numbers are in\n",[30,31,33],"a",{"href":32},"\u002Fresearch\u002Fvibe-coded-app-security-2026","our scan report",".",[36,37,39],"h2",{"id":38},"is-an-openai-api-key-exposed-in-your-frontend-a-problem","Is an OpenAI API key exposed in your frontend a problem?",[10,41,42],{},"Yes. This is the one where the alarm is right.",[10,44,45,46,49,50,53],{},"An OpenAI key is a ",[22,47,48],{},"bearer token",", and the word carries the whole explanation:\nwhoever bears it may use it. It travels in a header that reads\n",[14,51,52],{},"Authorization: Bearer sk-proj-…",", and OpenAI's servers ask for nothing else.\nNot which website sent it. Not which country it came from, or whether the sender\nis you.",[10,55,56],{},"Think of a train ticket rather than a passport. A conductor does not check whose\nname is on a ticket, because holding it is the entire qualification. That is what\nmakes a ticket worth stealing and a passport mostly not.",[10,58,59],{},"So a key printed into your app is a ticket you handed to every visitor. Most of\nthem will never look. The ones who do are usually not people at all: automated\nscrapers crawl public pages collecting key-shaped strings, and they do not need\nto know who you are to find yours.",[36,61,63],{"id":62},"why-the-environment-variable-did-not-hide-it","Why the environment variable did not hide it",[10,65,66],{},"Because a frontend build compiles environment variables into the file it ships.",[10,68,69,70,73,74,77,78,81],{},"This is the step that makes people confident the key is safe. You moved it out of\nyour code and into a ",[14,71,72],{},".env"," file, named it ",[14,75,76],{},"VITE_OPENAI_API_KEY"," or\n",[14,79,80],{},"NEXT_PUBLIC_OPENAI_API_KEY",", and now the code names a variable where the key\nused to be. Nothing about the shipped app changed. The build tool replaced that\nvariable with its value on the way out, and the value is sitting in the\nJavaScript your visitor downloads.",[10,83,84,85,88,89,91,92,95],{},"Vite's documentation says so plainly: variables carrying the ",[14,86,87],{},"VITE_"," prefix are\nexposed in client-side source code after bundling, and sensitive information like\nAPI keys should not be in one, because the values are bundled into your source.\nThe ",[14,90,87],{}," and ",[14,93,94],{},"NEXT_PUBLIC_"," prefixes are not a safe box. They are a\ndeclaration that you understand this variable is public.",[10,97,98],{},"An environment variable did do one real thing for you: it kept the key out of\nyour repository, where anyone reading your code would have met it. Your visitors\nnever read your code. They read the file your build produced from it.",[36,100,102],{"id":101},"why-you-cannot-restrict-it-the-way-you-restrict-a-google-key","Why you cannot restrict it the way you restrict a Google key",[10,104,105],{},"Because OpenAI does not offer a restriction of that kind.",[10,107,108,109,113],{},"If you have read about a Google API key in a frontend, you met a five-minute fix:\nopen the key in the Google Cloud console, set an HTTP referrer restriction, and\nthe key printed in your page works on your site and returns an error everywhere\nelse.\n",[30,110,112],{"href":111},"\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend","That advice is correct for a Google key",",\nand it does not carry across.",[10,115,116],{},"There is no field on an OpenAI key for \"only from yourapp.com\". No domain\nallowlist, no referrer check, and no IP restriction that survives a browser.\nWhat OpenAI gives you instead sits on the account behind the key: which project\nit belongs to, what that project is allowed to spend in a month, and whether the\nkey still exists. Those limit what a stolen key can cost. The key itself goes on\nworking from anywhere until you delete it.",[118,119],"diagram",{"alt":120,"caption":121,"src":122},"Two lanes. In the upper lane a Google key leaves two different websites and meets a solid barrier: the request from yourapp.com passes it and reaches Google, and the one from other.example is stopped and marked with a cross. In the lower lane an OpenAI key leaves the same two websites, there is no barrier drawn at all, and both requests reach api.openai.com.","The upper lane is the fix people are told about. In the lower lane there is nothing to draw, because OpenAI has no setting of that shape.","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fno-gate-to-set-1600x820.png",[36,124,126,127,130],{"id":125},"what-dangerouslyallowbrowser-actually-does","What ",[14,128,129],{},"dangerouslyAllowBrowser"," actually does",[10,132,133],{},"It switches off a guard, and its name is the documentation.",[10,135,136,137,139],{},"OpenAI's official JavaScript library refuses to run in a browser by default. The\nREADME says browser support is \"disabled by default to avoid exposing your secret\nAPI credentials\", and that enabling ",[14,138,129],{}," \"can be dangerous\nbecause it exposes your secret API credentials in the client-side code\".",[10,141,142,143,146,147,150],{},"If your app calls OpenAI from the browser, that option is set to ",[14,144,145],{},"true"," somewhere\nin your code, because the library will not start without it. Somebody typed the\nword ",[14,148,149],{},"dangerously"," to make an error message go away. That is usually how this\nfinding gets made, and the library did tell you first.",[10,152,153],{},"The option has real uses, and all of them are narrow: an internal tool where you\nknow every user, a temporary development key, a key scoped so tightly that\nspending it costs nothing. A public app on the open internet is none of those.",[36,155,157],{"id":156},"what-it-costs-when-someone-finds-your-key","What it costs when someone finds your key",[10,159,160],{},"A bill, and an app that stops working.",[10,162,163],{},"The bill is the part people expect. Somebody else's requests are charged to your\naccount, and model calls are not cheap by the standards of a side project. The\nusual way an owner notices is an invoice larger than last month's for no reason\nthey can point to.",[10,165,166],{},"The outage is the part they do not expect. Your account has rate limits, and a\nstranger's traffic spends them. Your own app starts returning errors during the\nhours somebody else is busy, which reads as a bug rather than as a theft, so\npeople spend a day debugging the wrong thing.",[10,168,169],{},"There is a third cost, and it depends on how the key was scoped. An API key\nauthenticates every call the project it belongs to can make, so a broadly scoped\nkey reaches whatever else is stored there: files uploaded to the account,\nfine-tuned models, assistants you built. Check what the key could actually reach\nbefore you decide this was only about money.",[36,171,173],{"id":172},"how-to-call-openai-from-your-app-without-shipping-the-key","How to call OpenAI from your app without shipping the key",[10,175,176],{},"Put something of yours between your visitor and OpenAI.",[10,178,179],{},"The shape is the same everywhere. Your app calls a small endpoint you own. That\nendpoint holds the key, calls OpenAI, and hands the answer back. The browser\nnever sees the key, because the key never leaves your server.",[118,181],{"alt":182,"caption":183,"src":184},"Two rows. In the top row a browser pane holds a red key value and a line runs from it straight to api.openai.com. In the bottom row the browser pane holds no key, a line runs to a server box that holds the key in teal, and a second line continues from the server to api.openai.com.","The same request in both rows. What changes is which side of your own server the key is standing on.","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fwho-holds-the-key-1600x720.png",[10,186,187],{},"Where that endpoint lives depends on what you built with:",[189,190,191,198,208],"ul",{},[192,193,194,197],"li",{},[22,195,196],{},"Supabase in your stack",": an Edge Function, with the key stored as a secret in the Supabase dashboard.",[192,199,200,203,204,207],{},[22,201,202],{},"Deployed on Vercel or Netlify",": a serverless function under ",[14,205,206],{},"\u002Fapi",", with the key in the project's server-side environment variables.",[192,209,210,213],{},[22,211,212],{},"Lovable, Bolt or Replit",": each has a secrets store of its own. The rule does not change, and neither does the trap: a store labelled secrets still ships the value to the browser if the code reading it runs there.",[10,215,216],{},"Two things are worth doing while you are in there. Give your endpoint a rate\nlimit, because an endpoint that calls OpenAI for anybody who asks is the same\nbill by a slower route. And set a monthly spend limit on the OpenAI project,\nwhich is the one control that puts a floor under the worst case.",[10,218,219],{},"If your app uses OpenAI's Realtime API for voice, the browser genuinely does need\na credential, and OpenAI documents how to give it one: your server mints a\nshort-lived client secret and hands the page that. The ticket still exists, it\nexpires in minutes, and your own server is what issued it.",[36,221,223],{"id":222},"how-to-find-every-key-your-app-is-shipping-for-free","How to find every key your app is shipping, for free",[10,225,226,227,229,230,233,234,237,238,241],{},"Start by hand, because it costs five minutes and needs nothing installed. View\nyour live site's source and search it for ",[14,228,16],{}," for an OpenAI key, ",[14,231,232],{},"sk-ant-","\nfor an Anthropic one, ",[14,235,236],{},"AIza"," for Google, and ",[14,239,240],{},"eyJ"," for a Supabase token.",[10,243,244],{},"What that misses is the JavaScript the page loads afterwards, which on a\nvibe-coded app is nearly all of it. Our scanner opens your app in a real browser,\nwaits for the bundles to arrive, and reads those instead of the HTML. It also\ndecodes each Supabase token and reports the role written inside it, so a key that\nbelongs in your page comes back marked correct rather than buried in a wall of\nred.",[10,246,247],{},"The key check is one of nine, and the other eight are why a grade tells you more\nthan a search does:",[118,249],{"alt":250,"caption":251,"src":252},"The Reeve mark above a grid of nine outlined icons, one per check: a key, a database cylinder, a document, a padlock, a lidded box, a pair of code brackets, a globe, a certificate rosette, and a calendar.","The nine read-only checks a scan runs, in the order they are listed below. Every one of them is read from outside, the way a stranger sees your app.","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fnine-checks-1600x420.png",[254,255,256,269],"table",{},[257,258,259],"thead",{},[260,261,262,266],"tr",{},[263,264,265],"th",{},"What the scan looks at",[263,267,268],{},"The question it answers",[270,271,272,281,289,300,308,316,324,332,340],"tbody",{},[260,273,274,278],{},[275,276,277],"td",{},"Secret keys in your code",[275,279,280],{},"Is a paid-API or admin key readable by anyone who looks?",[260,282,283,286],{},[275,284,285],{},"Database rules",[275,287,288],{},"Can a stranger read your users' rows without logging in?",[260,290,291,294],{},[275,292,293],{},"Private files",[275,295,296,297,299],{},"Are ",[14,298,72],{}," files or database dumps downloadable from a URL?",[260,301,302,305],{},[275,303,304],{},"Security headers",[275,306,307],{},"Are the browser-side protections switched on?",[260,309,310,313],{},[275,311,312],{},"Storage buckets",[275,314,315],{},"Can anyone list the files your users uploaded?",[260,317,318,321],{},[275,319,320],{},"Source maps",[275,322,323],{},"Is your original source code published alongside the app?",[260,325,326,329],{},[275,327,328],{},"Open APIs and CORS",[275,330,331],{},"Do your endpoints answer any website that asks?",[260,333,334,337],{},[275,335,336],{},"Certificate expiry",[275,338,339],{},"Is HTTPS valid, and not about to lapse on your visitors?",[260,341,342,345],{},[275,343,344],{},"Domain renewal",[275,346,347],{},"Is the name renewed before somebody else can take it?",[10,349,350],{},"You get a grade, a score and the counts on screen in about 20 seconds, with no\naccount. Give an email address and the detailed list comes with it, along with a\nfix written for your builder that you can paste straight in.",[10,352,353,354,357,358,362,363,367],{},"Three things it will not do, which are the reasons it is safe to point at a live\napp: it never logs in, it never writes anything, and it never keeps a key it\nfinds. An exposed secret is stored as a masked hint of the form ",[14,355,356],{},"sk-proj-…a1b2",",\nand the real value is discarded. ",[30,359,361],{"href":360},"\u002F#scan","Scan your app",", or read\n",[30,364,366],{"href":365},"\u002Fsecurity-scanner","what each of the nine checks looks at"," first.",[36,369,371],{"id":370},"what-to-do-right-now","What to do right now",[373,374,375],"key-takeaways",{},[189,376,377,380,383,386,389],{},[192,378,379],{},"Rotate the key first, in the OpenAI dashboard under API keys. Deleting it from your code closes nothing, because the old value is still in your version history and in every cached copy of your page.",[192,381,382],{},"Set a monthly spend limit on the project the key belongs to. It is the one control that caps what this or any later mistake can cost you.",[192,384,385],{},"Move the call behind an endpoint you own, and give that endpoint a rate limit of its own. A browser should never hold a key that spends money.",[192,387,388],{},"Read your usage page for the days the key was live. Rotation stops what happens next and says nothing about what already happened.",[192,390,391,392,394,395,397],{},"Keep a ",[14,393,87],{}," or ",[14,396,94],{}," prefix off anything you would mind a stranger reading. Those prefixes mean public, and your build tool takes them at their word.",[10,399,400,401,405,406,410,411,34],{},"If you would rather work through this in one sitting, the\n",[30,402,404],{"href":403},"\u002Fchecklist","10-minute security checklist"," covers this alongside the other things\nworth closing in a newly launched app. And for the wider question of which keys\nbelong in a browser at all, we have\n",[30,407,409],{"href":408},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","a guide to telling publishable keys from secret ones","\nand ",[30,412,414],{"href":413},"\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps","a census of what 30,998 apps actually shipped",{"title":416,"searchDepth":417,"depth":417,"links":418},"",3,[419,421,422,423,425,426,427,428],{"id":38,"depth":420,"text":39},2,{"id":62,"depth":420,"text":63},{"id":101,"depth":420,"text":102},{"id":125,"depth":420,"text":424},"What dangerouslyAllowBrowser actually does",{"id":156,"depth":420,"text":157},{"id":172,"depth":420,"text":173},{"id":222,"depth":420,"text":223},{"id":370,"depth":420,"text":371},"Security basics","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fcover-1200x630.png","A key-shaped value inside a page of app code, drawn long enough to run off the right edge of the pane holding 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.",false,"md",[436,439,442,445,448],{"q":437,"a":438},"Someone found my OpenAI key in my app. What is the first thing to do?","Rotate it. Open your OpenAI dashboard under API keys, create a new key, put the new one on your server, and delete the old one. Deleting it from your code is not the same thing, because the old value is still in your version history and in every cached copy of your page. After that, set a spend limit on the project and read your usage page for the days the key was live.",{"q":440,"a":441},"Can I restrict an OpenAI key to my own domain, the way I restrict a Google key?","No. A Google API key takes an HTTP referrer restriction that makes it work on your site and fail everywhere else, which is why a Google key in your page is usually fine. OpenAI offers nothing equivalent. There is no domain allowlist and no referrer check on an API key, so the only controls you have are on the account behind it: which project the key belongs to, what that project may spend, and whether the key still exists.",{"q":443,"a":444},"The OpenAI library has a dangerouslyAllowBrowser option. Does that make it safe?","No, and the name is the warning. OpenAI ships browser support switched off, and its own README says the option is dangerous because it exposes your secret API credentials in client-side code. Turning it on does not change what the browser can read; it only stops the library from refusing to start. The narrow cases it is meant for are internal tools with trusted users and short-lived development keys, not a public app.",{"q":446,"a":447},"How much can someone spend with a key they found?","As much as your account allows, which is why the spend limit matters more than the size of the bill you have seen so far. A stolen key draws on the same rate limits your app does, so the first symptom is often not the invoice at all: your own app starts failing while somebody else is busy. Set a monthly spend limit on the project and you have a ceiling on any of it.",{"q":449,"a":450},"I only used the key for a quick demo. Does it still matter?","Yes. A key stays live until somebody revokes it, and it does not know it was meant to be temporary. Automated scrapers collect key-shaped strings from public pages continuously, so the age of the demo works against you rather than for you. Deleting the key takes less time than deciding whether it was worth deleting.","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fcard-800x500.png",[453,454,455,456,129,457],"openai api key exposed frontend","hide openai api key react","call openai from frontend safely","openai api key security","openai api key leaked",{},true,"OpenAI API key exposed in your frontend?","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend","2026-09-03",{"title":5,"description":432},"blog\u002Fopenai-api-key-exposed-in-frontend",[466,467,468,469],"An OpenAI API key exposed in your frontend is the case where the alarm is right. No setting makes one safe in a browser.","It is a bearer token, so holding it is the whole qualification. There is no domain restriction to lock it to your own site, the way there is for a Google key.","Rotate it in the OpenAI dashboard today, move the call behind an endpoint you own, and put a spend limit on the project.","We found one in 33 of 30,998 scanned apps. Rare, and all 33 came out graded D or F.","2026-09-21","fXnJ_gKA0G1RjpTMVfkR-OPgpW-4076L-AALkvSaxpM",[473,480,486,491,497,503,509,515,521,527,533,539,545,551,557,563,569,575,581,587,593,594,599,605,611,617,623,629,635,641,647,653,659,664,670,676,681,687,693,699,704,710],{"path":474,"title":475,"description":476,"published":477,"category":478,"image":479,"draft":433},"\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":481,"title":482,"description":483,"published":484,"category":429,"image":485,"draft":433},"\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":487,"title":488,"description":489,"published":470,"category":429,"image":490,"draft":433},"\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.","\u002Fblog\u002Fis-lovable-safe\u002Fcard-800x500.png",{"path":492,"title":493,"description":494,"published":495,"category":429,"image":496,"draft":433},"\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":498,"title":499,"description":500,"published":501,"category":429,"image":502,"draft":433},"\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":504,"title":505,"description":506,"published":507,"category":429,"image":508,"draft":433},"\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":510,"title":511,"description":512,"published":513,"category":429,"image":514,"draft":433},"\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":516,"title":517,"description":518,"published":519,"category":429,"image":520,"draft":433},"\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":522,"title":523,"description":524,"published":525,"category":429,"image":526,"draft":433},"\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":528,"title":529,"description":530,"published":531,"category":478,"image":532,"draft":433},"\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":534,"title":535,"description":536,"published":537,"category":478,"image":538,"draft":433},"\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":540,"title":541,"description":542,"published":543,"category":478,"image":544,"draft":433},"\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":546,"title":547,"description":548,"published":549,"category":429,"image":550,"draft":433},"\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":552,"title":553,"description":554,"published":555,"category":429,"image":556,"draft":433},"\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":558,"title":559,"description":560,"published":561,"category":429,"image":562,"draft":433},"\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":564,"title":565,"description":566,"published":567,"category":429,"image":568,"draft":433},"\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":570,"title":571,"description":572,"published":573,"category":429,"image":574,"draft":433},"\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":576,"title":577,"description":578,"published":579,"category":429,"image":580,"draft":433},"\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":582,"title":583,"description":584,"published":585,"category":429,"image":586,"draft":433},"\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":588,"title":589,"description":590,"published":591,"category":429,"image":592,"draft":433},"\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":461,"title":5,"description":432,"published":462,"category":429,"image":451,"draft":433},{"path":413,"title":595,"description":596,"published":597,"category":429,"image":598,"draft":433},"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":600,"title":601,"description":602,"published":603,"category":478,"image":604,"draft":433},"\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":606,"title":607,"description":608,"published":609,"category":429,"image":610,"draft":433},"\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":612,"title":613,"description":614,"published":615,"category":478,"image":616,"draft":433},"\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":618,"title":619,"description":620,"published":621,"category":429,"image":622,"draft":433},"\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":624,"title":625,"description":626,"published":627,"category":429,"image":628,"draft":433},"\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":630,"title":631,"description":632,"published":633,"category":429,"image":634,"draft":433},"\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":636,"title":637,"description":638,"published":639,"category":429,"image":640,"draft":433},"\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":642,"title":643,"description":644,"published":645,"category":478,"image":646,"draft":433},"\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":648,"title":649,"description":650,"published":651,"category":478,"image":652,"draft":433},"\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":654,"title":655,"description":656,"published":657,"category":429,"image":658,"draft":433},"\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":111,"title":660,"description":661,"published":662,"category":429,"image":663,"draft":433},"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":665,"title":666,"description":667,"published":668,"category":478,"image":669,"draft":433},"\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":671,"title":672,"description":673,"published":674,"category":429,"image":675,"draft":433},"\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":677,"title":678,"description":679,"published":674,"category":429,"image":680,"draft":433},"\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":682,"title":683,"description":684,"published":685,"category":429,"image":686,"draft":433},"\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":688,"title":689,"description":690,"published":691,"category":478,"image":692,"draft":433},"\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":694,"title":695,"description":696,"published":697,"category":429,"image":698,"draft":433},"\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":700,"title":701,"description":702,"published":697,"category":478,"image":703,"draft":433},"\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":705,"title":706,"description":707,"published":708,"category":478,"image":709,"draft":433},"\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":408,"title":711,"description":712,"published":708,"category":429,"image":713,"draft":433},"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]