[{"data":1,"prerenderedAt":924},["ShallowReactive",2],{"blog-en-move-a-secret-to-an-edge-function":3,"blog-index-en":572},{"id":4,"title":5,"body":6,"category":527,"cover":528,"coverAlt":529,"description":530,"draft":531,"extension":532,"faq":533,"image":550,"keywords":551,"meta":560,"navigation":561,"ogTitle":562,"path":563,"published":564,"seo":565,"stem":566,"tldr":567,"updated":564,"__hash__":571},"blog_en\u002Fblog\u002Fmove-a-secret-to-an-edge-function.md","Hide an API key: move it to a Supabase Edge Function",{"type":7,"value":8,"toc":513},"minimark",[9,13,29,32,37,40,53,56,62,66,69,72,80,84,87,162,171,201,221,225,228,244,247,257,263,268,272,275,282,292,313,316,320,323,347,350,354,357,360,378,381,388,392,395,408,411,419,428,432,435,453,459,472,476,505],[10,11,12],"p",{},"Every guide about a leaked API key ends in the same place: move it to a server.\nThen it stops. You are left holding an app you built in Lovable, Bolt or Cursor,\nno server anywhere in sight, and a sentence that assumes you already know what to\ndo next.",[10,14,15,16,20,21,28],{},"Here is the part guide after guide leaves out. ",[17,18,19],"strong",{},"Moving the key into a Supabase\nEdge Function takes it out of your page, and by itself it does not stop a\nstranger using it."," Supabase's own documentation says why: the check a deployed\nfunction runs by default\n",[22,23,27],"a",{"href":24,"rel":25},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ffunctions\u002Fauth-headers",[26],"nofollow","also accepts your publishable key",",\nand your publishable key is in your frontend for anyone to copy. Hiding the key\nis the straightforward half. The half nobody writes about is deciding who your\nnew function answers to.",[10,30,31],{},"Think of the key as the master key to a stockroom. Right now it is taped to the\ninside of the shop window, where anyone who stops to look can read it. An Edge\nFunction is a back room with a hatch: the key goes in there, and visitors ask\nthrough the hatch instead of walking in and helping themselves. Whether that is\nan improvement depends on who the hatch opens for.",[33,34,36],"h2",{"id":35},"where-should-i-put-my-api-key-instead-of-the-frontend","Where should I put my API key instead of the frontend?",[10,38,39],{},"Anywhere that is not the browser. An Edge Function is the smallest version of\nthat you can get.",[10,41,42,43,47,48,52],{},"A browser cannot keep a secret. Everything your page needs in order to run gets\ndownloaded by every visitor, and a visitor can read all of it: the code, the\nimages, the values compiled into the code. That is not a fault in your builder.\nIt is what a web page is.\n",[22,44,46],{"href":45},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","Which API keys are safe in your frontend","\nis the long version; the short one is that an ",[49,50,51],"code",{},"sk_"," key, an OpenAI key or a\nSupabase secret key was never going to survive being shipped to a browser.",[10,54,55],{},"A Supabase Edge Function is a small piece of code that runs on Supabase's\nmachines. It can read secrets you set on your project, it answers at a web\naddress of its own, and your app calls it by name. You write one file and\nSupabase runs it, so there is no server for you to rent, patch or keep awake.",[57,58],"diagram",{"alt":59,"caption":60,"src":61},"Two arrangements. Above, the key sits inside the bundle every visitor downloads and the browser calls the vendor directly. Below, the bundle carries no key, the browser calls the Edge Function, and the function holds the key and calls the vendor.","Above: the key is in the file every visitor downloads, so every visitor has it. Below: the browser asks the function, and the key never leaves Supabase.","\u002Fblog\u002Fmove-a-secret-to-an-edge-function\u002Fwhere-the-key-lives-1600x800.png",[33,63,65],{"id":64},"rotate-the-key-you-already-published-before-you-move-anything","Rotate the key you already published, before you move anything",[10,67,68],{},"The key sitting in your frontend today is already public, and it stays public\nafter you take it out of the code.",[10,70,71],{},"Every visitor who loaded your site downloaded it. So did every crawler, and\nthere are automated ones walking the whole web looking for exactly these\nstrings. Your old bundle is also still in your version history and in whatever\ncaches have a copy of your page. Removing a line from a file you control does\nnothing about a value that has already been handed out.",[10,73,74,75,79],{},"So the order is: create a new key at the provider, hold on to it for a moment,\nand revoke the old one once the function below is live. Then read your billing\npage and your usage logs for the whole period the old key was out there.\nRotating stops what happens next; it has no effect on what already happened. If\nthe key was a Supabase secret key, ",[22,76,78],{"href":77},"\u002Fblog\u002Frotate-supabase-service-role-key","the order to work\nin"," has a wrinkle worth reading first.",[33,81,83],{"id":82},"the-four-steps","The four steps",[10,85,86],{},"Store the secret, write the function, deploy it, then change your app to call\nthe function instead of the vendor.",[88,89,90,106],"table",{},[91,92,93],"thead",{},[94,95,96,100,103],"tr",{},[97,98,99],"th",{},"Step",[97,101,102],{},"On the command line",[97,104,105],{},"In the dashboard",[107,108,109,123,136,149],"tbody",{},[94,110,111,115,120],{},[112,113,114],"td",{},"1. Store the secret",[112,116,117],{},[49,118,119],{},"supabase secrets set MY_API_KEY=…",[112,121,122],{},"Edge Functions → Secrets, then Key and Value",[94,124,125,128,133],{},[112,126,127],{},"2. Write the function",[112,129,130],{},[49,131,132],{},"supabase functions new forward-request",[112,134,135],{},"Edge Functions → Deploy a new function → Via Editor",[94,137,138,141,146],{},[112,139,140],{},"3. Deploy it",[112,142,143],{},[49,144,145],{},"supabase functions deploy",[112,147,148],{},"The Deploy button under the editor",[94,150,151,154,159],{},[112,152,153],{},"4. Call it from your app",[112,155,156],{},[49,157,158],{},"supabase.functions.invoke('…')",[112,160,161],{},"Same, in your app's code",[10,163,164,165,170],{},"Inside the function the secret arrives as an environment variable, which is a\nnamed value the code can read and nobody outside can. Supabase's docs for\n",[22,166,169],{"href":167,"rel":168},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ffunctions\u002Fsecrets",[26],"Edge Function secrets"," have\nthe full reference, and three details in it save a confusing hour:",[172,173,174,185,191],"ul",{},[175,176,177,184],"li",{},[17,178,179,180,183],{},"A secret name cannot start with ",[49,181,182],{},"SUPABASE_","."," That prefix is reserved for\nthe values Supabase sets for you, and both the dashboard and the API refuse it.",[175,186,187,190],{},[17,188,189],{},"A new secret is readable immediately."," You do not redeploy the function\nafter changing one.",[175,192,193,196,197,200],{},[17,194,195],{},"Local and production are separate."," The local stack reads\n",[49,198,199],{},"supabase\u002Ffunctions\u002F.env",", which is a different place from the secrets on your\nlive project, so set the value in both or the function works on your machine\nand fails once deployed.",[10,202,203,204,207,208,211,212,215,216,220],{},"Then delete the old variable from your frontend. If it was named ",[49,205,206],{},"VITE_",",\n",[49,209,210],{},"NEXT_PUBLIC_"," or ",[49,213,214],{},"EXPO_PUBLIC_",", that prefix was an instruction to compile the\nvalue into the bundle, and ",[22,217,219],{"href":218},"\u002Fblog\u002Fvite-and-next-public-env-vars","the prefix is the whole\nstory"," of how it got there.",[33,222,224],{"id":223},"does-a-token-requirement-mean-only-my-users-can-call-it","Does a token requirement mean only my users can call it?",[10,226,227],{},"No, and this is the one thing in this article worth reading twice.",[10,229,230,231,234,235,238,239,243],{},"Supabase turns a check called ",[49,232,233],{},"verify_jwt"," on by default. It inspects the\n",[49,236,237],{},"Authorization"," header before your code runs and refuses the request if nothing\nvalid is there. That sounds like a door only your users can open. It is not,\nbecause Supabase also documents that the same check\n",[22,240,242],{"href":24,"rel":241},[26],"accepts a publishable or secret key","\non either header, for compatibility with older projects, and adds plainly that\nthe check alone does not authenticate a caller who sends only an API key.",[10,245,246],{},"Your publishable key is in your frontend. That is correct and it is meant to be\nthere. It also means anyone who opens your site, reads the key and sends it to\nyour new function gets past the platform check and into your code.",[248,249,251],"callout",{"type":250},"warn",[10,252,253,256],{},[17,254,255],{},"This is how a moved key becomes an open proxy."," The key is no longer in your\npage, so a scanner looking for key-shaped strings finds nothing, and the OpenAI\nor Stripe bill carries on growing. What changed is that the requests now go\nthrough you. Before, a stranger had your key; after, a stranger has a web\naddress of yours that uses your key on their behalf, with no limit on how often.",[10,258,259,260,262],{},"In the stockroom, ",[49,261,233],{}," is a doorman who checks that you are holding a\nvisitor pass. Every visitor has one, because you hand them out at the door. The\nreceptionist at the hatch is a different job, and it is the one that asks which\nvisitor you are.",[57,264],{"alt":265,"caption":266,"src":267},"Two callers reaching the same Edge Function. A stranger sending only the publishable key passes the platform verify_jwt check and is then refused by the check inside the function. A signed-in user sending a session token passes both and reaches the vendor API.","Both callers get past the platform check. Only the second one gets past a check on who is calling, and that second check is the one you have to add.","\u002Fblog\u002Fmove-a-secret-to-an-edge-function\u002Ftwo-checks-1600x860.png",[33,269,271],{"id":270},"locking-the-function-down-so-you-have-not-built-an-open-proxy","Locking the function down, so you have not built an open proxy",[10,273,274],{},"Decide which callers your function accepts, and write that decision into the\nfunction.",[10,276,277,278,281],{},"Supabase ships a wrapper for this, so it is a line rather than a project. Set\n",[49,279,280],{},"auth: 'user'"," and the function accepts a signed-in user's token and hands your\ncode a database client already scoped to that user's Row Level Security rules:",[283,284,290],"pre",{"className":285,"code":287,"language":288,"meta":289},[286],"language-ts","import { withSupabase } from 'npm:@supabase\u002Fserver@1'\n\nexport default {\n  fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {\n    \u002F\u002F ctx.userClaims is who is calling. Reject anything you do not want here,\n    \u002F\u002F then call the vendor with the secret from the environment.\n    return Response.json({ ok: true })\n  }),\n}\n","ts","",[49,291,287],{"__ignoreMap":289},[10,293,294,295,300,301,304,305,308,309,312],{},"The ",[22,296,299],{"href":297,"rel":298},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ffunctions\u002Fauth",[26],"Securing Edge Functions","\npage lists the other modes, and two of them matter for ordinary apps. A function\ncalled by another machine rather than by a browser uses ",[49,302,303],{},"auth: 'secret'",", with\nthe secret key sent on the ",[49,306,307],{},"apikey"," header. A function that receives webhooks\nfrom Stripe or GitHub cannot use either, because those providers have no token\nof yours; it sets ",[49,310,311],{},"verify_jwt = false"," and checks the provider's signature\ninside the handler instead.",[10,314,315],{},"Whichever you pick, decide deliberately what happens when a caller you did not\nexpect arrives. Supabase's example of a function that may accept every caller is\na health check, and a health check costs nothing when a stranger calls it. A\nfunction that forwards a paid API call bills you for each one.",[33,317,319],{"id":318},"can-i-use-service_role-in-an-edge-function","Can I use service_role in an Edge Function?",[10,321,322],{},"Yes. A function is the one place a Supabase secret key belongs.",[10,324,325,326,329,330,333,334,337,338,341,342,346],{},"You do not paste it in, either. Supabase puts the project's keys into the\nfunction's environment for you, so the code reads them from there rather than\nfrom a secret you set. Newer projects get ",[49,327,328],{},"SUPABASE_SECRET_KEYS"," and\n",[49,331,332],{},"SUPABASE_PUBLISHABLE_KEYS",", each a small dictionary of named keys; older\nprojects get ",[49,335,336],{},"SUPABASE_SERVICE_ROLE_KEY"," and ",[49,339,340],{},"SUPABASE_ANON_KEY"," under their\noriginal names. ",[22,343,345],{"href":344},"\u002Fblog\u002Fsupabase-new-api-keys","What changed when Supabase renamed its\nkeys"," covers which pair you have.",[10,348,349],{},"Use the secret key for work that genuinely has to see every row, such as writing\nan audit record the user must not be able to edit. For anything a user is\nreading about themselves, use their own token and let your Row Level Security\nrules do the filtering, which is what they are for.",[33,351,353],{"id":352},"verifying-it-from-the-browser-which-is-the-only-proof","Verifying it from the browser, which is the only proof",[10,355,356],{},"Load your live site, open the network tab, and look at what your browser\nactually sends.",[10,358,359],{},"The two things to check:",[172,361,362,372],{},[175,363,364,367,368,371],{},[17,365,366],{},"No request carries the key."," Click through the part of your app that used\nto call the vendor. The outgoing request should go to\n",[49,369,370],{},"…supabase.co\u002Ffunctions\u002Fv1\u002Fyour-function",", and the only credential anywhere in\nit should be your publishable key or your user's own token.",[175,373,374,377],{},[17,375,376],{},"The key is not in the downloaded code."," Use your browser's search across\nall loaded files and paste in the first dozen characters of the old key. No\nresult is the answer you want.",[10,379,380],{},"A rebuild and a redeploy is what removes a value from the bundle, so a key that\nstill turns up in the search usually means the frontend went out before the\nvariable came out of it.",[10,382,383,384,183],{},"Our free scan does the second check from outside and names what it can read in\nyour live bundle, including which Supabase key you shipped. It takes about 20\nseconds and needs no account: ",[22,385,387],{"href":386},"\u002Fsecurity-scanner","scan your app",[33,389,391],{"id":390},"doing-this-from-inside-lovable-bolt-or-replit","Doing this from inside Lovable, Bolt or Replit",[10,393,394],{},"Use the Supabase dashboard. There is no terminal step in it anywhere.",[10,396,397,398,401,402,407],{},"Open your project, choose Edge Functions in the sidebar, then ",[17,399,400],{},"Deploy a new\nfunction → Via Editor",". Supabase's\n",[22,403,406],{"href":404,"rel":405},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ffunctions\u002Fquickstart-dashboard",[26],"dashboard quickstart","\nhas the walkthrough with screenshots, and the templates it offers include one\nfor proxying an AI provider, which is the exact shape of this job. Deployment\ntakes somewhere between ten and thirty seconds, and the function is then live at\nan address of its own. Your secret goes on the Edge Function Secrets page in the\nsame section.",[10,409,410],{},"One caveat that is worth knowing before you rely on it: Supabase says the\ndashboard editor has no version control, no versioning and no rollbacks, and\nrecommends it for quick work rather than for code you intend to keep. Pressing\nDeploy overwrites what was there. If the function ends up doing something you\ncare about, download it from that page and keep the file somewhere.",[10,412,413,414,418],{},"There is also a Replit-shaped version of this question, because Replit has its\nown secrets store and your app runs its own server there. ",[22,415,417],{"href":416},"\u002Fblog\u002Freplit-secrets-explained","What Replit Secrets\nactually do"," is that version.",[248,420,422],{"type":421},"note",[10,423,424,427],{},[17,425,426],{},"If the call comes from a browser, the function needs CORS headers."," A browser\nwill not let your page read a response from a different address unless that\naddress says it may. Supabase's dashboard templates include the headers; a\nfunction written from scratch does not, and the symptom is a request that looks\nfine in the function logs and fails in your app.",[33,429,431],{"id":430},"when-an-edge-function-is-the-wrong-answer","When an Edge Function is the wrong answer",[10,433,434],{},"Two cases, and in both of them moving the key costs you work and buys nothing.",[10,436,437,440,441,444,445,448,449,452],{},[17,438,439],{},"The key was publishable."," A Stripe ",[49,442,443],{},"pk_live_"," key, a Supabase publishable or\n",[49,446,447],{},"anon"," key, a Mapbox public token: these are designed to sit in a browser, and\nthe protection is somewhere else. Putting one behind a function adds a hop and\nremoves no risk. ",[22,450,451],{"href":45},"Which keys those\nare"," is the list.",[10,454,455,458],{},[17,456,457],{},"The key can be restricted to your own site."," Google's browser keys are the\ncommon example: you can limit one to your domains in the Google console, which\nis the fix Google supports for exactly this situation. The key is still readable\nand it stops being useful to anyone who copies it.",[10,460,461,462,466,467,471],{},"Everything else belongs on a server. There is no publishable variant of an\nOpenAI or Anthropic key, which is why ",[22,463,465],{"href":464},"\u002Fblog\u002Fopenai-api-key-exposed-in-frontend","a model provider key in your\nfrontend"," has no setting that saves\nit, and no amount of minifying hides a\n",[22,468,470],{"href":469},"\u002Fblog\u002Fstripe-secret-key-in-frontend","Stripe secret key"," from somebody reading\nyour page.",[33,473,475],{"id":474},"what-to-do-right-now","What to do right now",[477,478,479],"key-takeaways",{},[172,480,481,484,490,493,499,502],{},[175,482,483],{},"Rotate the key first. Create a new one at the provider, put it in the function's secret, and revoke the old one once the function is live.",[175,485,486,487,489],{},"Store the secret on your Supabase project, not in your repository and not in a ",[49,488,206],{}," variable. Set it for local development and for production separately.",[175,491,492],{},"Deploy a function that uses the secret, and change your app to call the function by name instead of calling the vendor.",[175,494,495,496,498],{},"Add a check on who is calling. ",[49,497,233],{}," on its own accepts the public key from your own frontend, so it does not keep a stranger out.",[175,500,501],{},"Delete the old variable, rebuild, and confirm from your browser's network tab that nothing going out carries the key.",[175,503,504],{},"Read your billing and usage for the whole period the old key was public. Rotation stops the next charge and not the last one.",[10,506,507,508,512],{},"If you would rather work through your whole app rather than one key, the\n",[22,509,511],{"href":510},"\u002Fchecklist","10-minute security checklist"," covers this alongside the other\nthings worth closing in a newly launched app.",{"title":289,"searchDepth":514,"depth":514,"links":515},3,[516,518,519,520,521,522,523,524,525,526],{"id":35,"depth":517,"text":36},2,{"id":64,"depth":517,"text":65},{"id":82,"depth":517,"text":83},{"id":223,"depth":517,"text":224},{"id":270,"depth":517,"text":271},{"id":318,"depth":517,"text":319},{"id":352,"depth":517,"text":353},{"id":390,"depth":517,"text":391},{"id":430,"depth":517,"text":431},{"id":474,"depth":517,"text":475},"Security basics","\u002Fblog\u002Fmove-a-secret-to-an-edge-function\u002Fcover-1200x630.png","A page with a key-shaped hole cut out of it, and behind it a sealed box with a closed lock and the key visible through a small window.","Hiding an API key means moving it off the browser, and a Supabase Edge Function is the smallest place to put it. Two steps around the move matter more.",false,"md",[534,536,539,542,545,547],{"q":36,"a":535},"Anywhere that is not the browser. A Supabase Edge Function is the smallest option: you store the key as a secret on your project, write one small file that uses it, and your app calls that function by name instead of calling the vendor directly. Other answers that work the same way are a serverless route on your host, or any server of your own. What they have in common is that the key is read where your visitor cannot see it.",{"q":537,"a":538},"What is a Supabase Edge Function?","A small piece of code that runs on Supabase machines rather than in your visitor browser. It answers at a web address of its own, it can read secrets you set on your project, and your app calls it with supabase.functions.invoke. You write one file and Supabase runs it, so there is no server for you to rent or maintain.",{"q":540,"a":541},"Do I need to rotate my key after moving it?","Yes, and do it first. The key that was in your frontend has been downloaded by every visitor and by every crawler that read your site, and taking it out of the code does nothing about the copies. Create a new key at the provider, put the new one in your Edge Function secret, and revoke the old one. Then check billing and usage for the period the old key was live.",{"q":543,"a":544},"Can anyone call my Edge Function?","By default a function refuses a request with no token at all, but that check is weaker than it sounds. Supabase documents that the platform check also accepts your publishable or secret key, and your publishable key is in your frontend where anyone can read it. So the check stops an empty request and not a determined one. To accept only your signed-in users, verify the caller inside the function.",{"q":319,"a":546},"Yes. An Edge Function is the one place a Supabase secret key legitimately belongs, and Supabase puts the project keys into the function environment for you, so you never paste one in. Use it for work that has to see every row, and use the caller own token for anything that should obey your Row Level Security rules.",{"q":548,"a":549},"Can I do this without a terminal?","Yes. The Supabase dashboard has an Edge Functions section where you can write a function in the browser, press Deploy, and set your secret on the Edge Function Secrets page. Supabase notes that the dashboard editor keeps no version history, so it suggests the editor for quick work and the command line for anything you intend to keep.","\u002Fblog\u002Fmove-a-secret-to-an-edge-function\u002Fcard-800x500.png",[552,553,554,555,556,557,558,559],"hide api key supabase edge function","supabase edge function secrets","supabase secrets set","move api key to backend","call openai from edge function","keep api key off the frontend","supabase edge function environment variables","where to put a secret key",{},true,"Hide an API key in a Supabase Edge Function","\u002Fblog\u002Fmove-a-secret-to-an-edge-function","2026-10-05",{"title":5,"description":530},"blog\u002Fmove-a-secret-to-an-edge-function",[568,569,570],"Nothing in a browser can keep a secret, so hiding an API key means moving it somewhere that can. A Supabase Edge Function is the smallest server you can get.","The move is four steps. Rotate the key you already published before you start, because the copy in your old bundle stays readable after you take it out.","A deployed function demands a token by default, and the public key in your own frontend satisfies that demand. Checking who is calling is a separate step, and it is the one that stops a stranger spending your quota.","oS8DlIcfw-zOUoeISD8gQS9TP1b5rJVClRCJsdv9ymI",[573,579,585,591,597,603,609,610,616,622,628,634,640,646,653,659,665,670,676,682,688,694,700,706,711,716,722,728,734,740,746,752,758,764,770,775,781,787,793,799,804,810,816,821,827,833,839,845,851,857,863,869,875,881,887,892,897,903,909,914,920],{"path":574,"title":575,"description":576,"published":577,"category":527,"image":578,"draft":531},"\u002Fblog\u002Fstorage-upload-violates-row-level-security","\"Row-level security policy for table objects\" on upload","\"New row violates row-level security policy for table objects\" means your upload has no insert rule. Making the bucket public does not add one.","2026-10-11","\u002Fblog\u002Fstorage-upload-violates-row-level-security\u002Fcard-800x500.png",{"path":580,"title":581,"description":582,"published":583,"category":527,"image":584,"draft":531},"\u002Fblog\u002Fbase44-security-scan","Base44 security scan: the one thing only it can see","The Base44 security scan checks seven kinds of problem from inside your app. Here is the half it reads that nothing outside can, and the half it never looks at.","2026-10-10","\u002Fblog\u002Fbase44-security-scan\u002Fcard-800x500.png",{"path":586,"title":587,"description":588,"published":589,"category":527,"image":590,"draft":531},"\u002Fblog\u002Fapi-key-leaked-what-to-do","Your API key leaked. Here is the order to do things in","An API key leaked and you want to know what to do first. Not every key in your frontend is one, and the order matters more than the speed.","2026-10-09","\u002Fblog\u002Fapi-key-leaked-what-to-do\u002Fcard-800x500.png",{"path":592,"title":593,"description":594,"published":595,"category":527,"image":596,"draft":531},"\u002Fblog\u002Finfinite-recursion-in-policy-for-relation","\"Infinite recursion detected in policy\" without disabling RLS","\"Infinite recursion detected in policy for relation\" means your Supabase policy asked the table it protects. Here is how to break the circle.","2026-10-08","\u002Fblog\u002Finfinite-recursion-in-policy-for-relation\u002Fcard-800x500.png",{"path":598,"title":599,"description":600,"published":601,"category":527,"image":602,"draft":531},"\u002Fblog\u002Fno-api-key-found-in-request","\"No API key found in request\" in Supabase, and the wrong fix","\"No API key found in request\" means your Supabase request arrived without a key. Most answers you find point at your database rules instead.","2026-10-07","\u002Fblog\u002Fno-api-key-found-in-request\u002Fcard-800x500.png",{"path":604,"title":605,"description":606,"published":607,"category":527,"image":608,"draft":531},"\u002Fblog\u002Fis-base44-safe","Is Base44 safe? What 5,442 live Base44 apps showed","Is Base44 safe? We ran nine checks on 5,442 live Base44 apps. Almost all scored B for reasons the owner never chose, and one question nobody can answer.","2026-10-06","\u002Fblog\u002Fis-base44-safe\u002Fcard-800x500.png",{"path":563,"title":5,"description":530,"published":564,"category":527,"image":550,"draft":531},{"path":611,"title":612,"description":613,"published":614,"category":527,"image":615,"draft":531},"\u002Fblog\u002Fenv-file-exposed-on-your-server","Is your .env file exposed? The twelve paths to check","Is your .env file exposed on your own web server? Twelve addresses tell you in a minute, and a hit means everything in the file is already public.","2026-10-04","\u002Fblog\u002Fenv-file-exposed-on-your-server\u002Fcard-800x500.png",{"path":617,"title":618,"description":619,"published":620,"category":527,"image":621,"draft":531},"\u002Fblog\u002Fis-v0-safe","v0 security: all 1,790 v0 apps we scanned got an A","v0 security, measured on 1,790 live v0 apps: every one graded A. Only 17 named a database, and that is most of what the A is measuring.","2026-10-03","\u002Fblog\u002Fis-v0-safe\u002Fcard-800x500.png",{"path":623,"title":624,"description":625,"published":626,"category":527,"image":627,"draft":531},"\u002Fblog\u002Fis-bolt-safe","Is Bolt safe? What 1,123 live Bolt apps showed","Is Bolt safe? We ran nine checks on 1,123 live Bolt apps. The hosting came back clean. The findings were API keys and open tables inside the apps.","2026-10-02","\u002Fblog\u002Fis-bolt-safe\u002Fcard-800x500.png",{"path":629,"title":630,"description":631,"published":632,"category":527,"image":633,"draft":531},"\u002Fblog\u002Fis-supabase-down-or-your-app","Is Supabase down, or is it your app? How to tell","Is Supabase down, or is it your app? A paused project, a full database and a bug of your own all look like an outage. Four checks tell them apart.","2026-10-01","\u002Fblog\u002Fis-supabase-down-or-your-app\u002Fcard-800x500.png",{"path":635,"title":636,"description":637,"published":638,"category":527,"image":639,"draft":531},"\u002Fblog\u002Fdoes-supabase-encrypt-my-data","Does Supabase encrypt my data? Yes. Here is what it stops","Does Supabase encrypt data? Yes: AES-256 at rest, TLS in transit, SOC 2 and ISO 27001 audited. What each one covers, and the leak none of them stops.","2026-09-30","\u002Fblog\u002Fdoes-supabase-encrypt-my-data\u002Fcard-800x500.png",{"path":641,"title":642,"description":643,"published":644,"category":527,"image":645,"draft":531},"\u002Fblog\u002Fsupabase-free-plan-limits","Supabase free plan limits, and what happens at each one","The Supabase free plan limits you to 500 MB of database and 5 GB of egress, with no backups. What happens as you cross each line, and what $25 changes.","2026-09-29","\u002Fblog\u002Fsupabase-free-plan-limits\u002Fcard-800x500.png",{"path":647,"title":648,"description":649,"published":650,"category":651,"image":652,"draft":531},"\u002Fblog\u002Ftest-your-supabase-backup","Test your Supabase backup before the day you need it","How to test your Supabase backup: restore it into a spare project, compare the row counts, sign in, and check for the line a cut-off file is missing.","2026-09-28","Backups","\u002Fblog\u002Ftest-your-supabase-backup\u002Fcard-800x500.png",{"path":654,"title":655,"description":656,"published":657,"category":651,"image":658,"draft":531},"\u002Fblog\u002Fsupabase-backup-github-action","Free Supabase backup with a GitHub Action, and the catch","A Supabase backup GitHub Action costs nothing and suits a lot of apps. The workflow, the connection string that works on GitHub, and the egress each run uses.","2026-09-27","\u002Fblog\u002Fsupabase-backup-github-action\u002Fcard-800x500.png",{"path":660,"title":661,"description":662,"published":663,"category":651,"image":664,"draft":531},"\u002Fblog\u002Fdownload-your-supabase-backup","Why you can't download your Supabase backup","You can't download your Supabase backup on a current project, because the daily copy is a physical snapshot. How to tell, and how to hold a copy of your own.","2026-09-26","\u002Fblog\u002Fdownload-your-supabase-backup\u002Fcard-800x500.png",{"path":666,"title":667,"description":668,"published":663,"category":527,"image":669,"draft":531},"\u002Fblog\u002Fsupabase-permission-denied-for-table","Supabase \"permission denied for table\": the missing grant","From October 30, a new Supabase table answers \"permission denied for table\" until you grant access. The grant the email shows is half the fix.","\u002Fblog\u002Fsupabase-permission-denied-for-table\u002Fcard-800x500.png",{"path":671,"title":672,"description":673,"published":674,"category":527,"image":675,"draft":531},"\u002Fblog\u002Fopen-api-endpoint-exposed","Is an open API endpoint a security problem? Look at the JSON","Your scan flagged an open API endpoint. Whether it matters depends on what came back, and most of the ones we found were the platform's own.","2026-09-25","\u002Fblog\u002Fopen-api-endpoint-exposed\u002Fcard-800x500.png",{"path":677,"title":678,"description":679,"published":680,"category":527,"image":681,"draft":531},"\u002Fblog\u002Flovable-security-scan","Lovable security scan: the one thing it cannot prove","Lovable security scan: what the Quick and Deep scans check, when each one runs, and the one thing no scan from inside your project can prove.","2026-09-24","\u002Fblog\u002Flovable-security-scan\u002Fcard-800x500.png",{"path":683,"title":684,"description":685,"published":686,"category":651,"image":687,"draft":531},"\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","\u002Fblog\u002Fsupabase-backup-auth-users\u002Fcard-800x500.png",{"path":689,"title":690,"description":691,"published":692,"category":527,"image":693,"draft":531},"\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":695,"title":696,"description":697,"published":698,"category":527,"image":699,"draft":531},"\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":701,"title":702,"description":703,"published":704,"category":527,"image":705,"draft":531},"\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":469,"title":707,"description":708,"published":709,"category":527,"image":710,"draft":531},"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":218,"title":712,"description":713,"published":714,"category":527,"image":715,"draft":531},"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":717,"title":718,"description":719,"published":720,"category":527,"image":721,"draft":531},"\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":723,"title":724,"description":725,"published":726,"category":527,"image":727,"draft":531},"\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":729,"title":730,"description":731,"published":732,"category":527,"image":733,"draft":531},"\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":735,"title":736,"description":737,"published":738,"category":651,"image":739,"draft":531},"\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":741,"title":742,"description":743,"published":744,"category":651,"image":745,"draft":531},"\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":747,"title":748,"description":749,"published":750,"category":651,"image":751,"draft":531},"\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":753,"title":754,"description":755,"published":756,"category":527,"image":757,"draft":531},"\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":759,"title":760,"description":761,"published":762,"category":527,"image":763,"draft":531},"\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":765,"title":766,"description":767,"published":768,"category":527,"image":769,"draft":531},"\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":77,"title":771,"description":772,"published":773,"category":527,"image":774,"draft":531},"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":776,"title":777,"description":778,"published":779,"category":527,"image":780,"draft":531},"\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":782,"title":783,"description":784,"published":785,"category":527,"image":786,"draft":531},"\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":788,"title":789,"description":790,"published":791,"category":527,"image":792,"draft":531},"\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":794,"title":795,"description":796,"published":797,"category":527,"image":798,"draft":531},"\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":464,"title":800,"description":801,"published":802,"category":527,"image":803,"draft":531},"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":805,"title":806,"description":807,"published":808,"category":527,"image":809,"draft":531},"\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":811,"title":812,"description":813,"published":814,"category":651,"image":815,"draft":531},"\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":416,"title":817,"description":818,"published":819,"category":527,"image":820,"draft":531},"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":822,"title":823,"description":824,"published":825,"category":651,"image":826,"draft":531},"\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":828,"title":829,"description":830,"published":831,"category":527,"image":832,"draft":531},"\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":834,"title":835,"description":836,"published":837,"category":527,"image":838,"draft":531},"\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":840,"title":841,"description":842,"published":843,"category":527,"image":844,"draft":531},"\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":846,"title":847,"description":848,"published":849,"category":527,"image":850,"draft":531},"\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":852,"title":853,"description":854,"published":855,"category":651,"image":856,"draft":531},"\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":858,"title":859,"description":860,"published":861,"category":651,"image":862,"draft":531},"\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":864,"title":865,"description":866,"published":867,"category":527,"image":868,"draft":531},"\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":870,"title":871,"description":872,"published":873,"category":527,"image":874,"draft":531},"\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":876,"title":877,"description":878,"published":879,"category":651,"image":880,"draft":531},"\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":882,"title":883,"description":884,"published":885,"category":527,"image":886,"draft":531},"\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":888,"title":889,"description":890,"published":885,"category":527,"image":891,"draft":531},"\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":344,"title":893,"description":894,"published":895,"category":527,"image":896,"draft":531},"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":898,"title":899,"description":900,"published":901,"category":651,"image":902,"draft":531},"\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":904,"title":905,"description":906,"published":907,"category":527,"image":908,"draft":531},"\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":910,"title":911,"description":912,"published":907,"category":651,"image":913,"draft":531},"\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":915,"title":916,"description":917,"published":918,"category":651,"image":919,"draft":531},"\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":45,"title":921,"description":922,"published":918,"category":527,"image":923,"draft":531},"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",1791705957970]