[{"data":1,"prerenderedAt":839},["ShallowReactive",2],{"blog-pt-vite-and-next-public-env-vars":3,"blog-index-pt":598},{"id":4,"title":5,"body":6,"category":550,"cover":551,"coverAlt":552,"description":553,"draft":554,"extension":555,"faq":556,"image":575,"keywords":576,"meta":586,"navigation":587,"ogTitle":588,"path":589,"published":590,"seo":591,"stem":592,"tldr":593,"updated":590,"__hash__":597},"blog_pt\u002Fblog\u002Fvite-and-next-public-env-vars.md","Variáveis de ambiente do Vite expostas: o prefixo diz publique isto",{"type":7,"value":8,"toc":536},"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,420,424,427,434,454,462,470,474,517,520,528],[10,11,12,13,17,18,21,22,24],"p",{},"Alguém abriu o seu app do Lovable, apertou F12 e encontrou no código um valor\nque você tem certeza de ter colocado em um arquivo ",[14,15,16],"code",{},".env",". Ou você pediu uma\nfunção ao builder, ele escreveu uma linha começando com ",[14,19,20],{},"VITE_",", e algo que\nvocê leu desde então diz que esse prefixo é por onde as chaves vazam. O\narquivo tem ",[14,23,16],{}," no nome e todo tutorial diz para nunca compartilhá-lo.\nVariáveis de ambiente do Vite expostas a cada visitante soam como um bug do\nVite.",[10,26,27,28,32,33,35,36,39],{},"Aqui está a parte que guia após guia explica errado: ",[29,30,31],"strong",{},"nada falhou em\nesconder esse valor."," ",[14,34,20],{}," e ",[14,37,38],{},"NEXT_PUBLIC_"," são uma instrução para a sua\nferramenta de build, e a instrução é colocar o valor no app que cada\nvisitante baixa. A ferramenta leu a etiqueta e fez o que ela dizia.",[10,41,42],{},"Um build empacota o seu app em uma caixa, e cada visitante recebe uma cópia\nda caixa. O prefixo é uma etiqueta em um valor que diz: empacote isto também.\nO resto deste artigo é sobre para quais valores essa etiqueta está certa, por\nque a IA a coloca nos errados, e como ver o que está na sua própria caixa.",[44,45,47],"h2",{"id":46},"as-variáveis-de-ambiente-do-vite-ficam-expostas-aos-visitantes","As variáveis de ambiente do Vite ficam expostas aos visitantes?",[10,49,50,51,53],{},"As que começam com ",[14,52,20],{},", sim, e é para isso que o prefixo existe.",[10,55,56,57,59,60,63,64,67,68,71],{},"O Vite, a ferramenta de build por trás da maioria dos apps do Lovable e do\nBolt, lê o seu arquivo ",[14,58,16],{}," toda vez que faz o build. Uma variável chamada\n",[14,61,62],{},"VITE_SUPABASE_URL"," é copiada para o bundle, o arquivo JavaScript comprimido\nque cada visitante baixa, onde o seu código a lê como\n",[14,65,66],{},"import.meta.env.VITE_SUPABASE_URL",". Uma variável chamada ",[14,69,70],{},"DB_PASSWORD",", sem\nprefixo, chega vazia ao navegador. A própria documentação do Vite diz que os\nvalores com prefixo são empacotados no seu código-fonte na hora do build e\nnão deveriam conter chaves de API.",[10,73,74,75,77,78,81,82,85],{},"O Next.js, com o qual o v0 constrói, tem a mesma regra com outro nome:\n",[14,76,38],{},". A documentação dele descreve o valor como inlined, uma string\nfixa escrita no bundle do navegador na hora do build. O Expo usa\n",[14,79,80],{},"EXPO_PUBLIC_"," e avisa com as mesmas palavras. Projetos antigos do Create\nReact App usam ",[14,83,84],{},"REACT_APP_",". Cada prefixo diz a mesma coisa à sua ferramenta\nde build: este vai na caixa.",[44,87,89,90,92],{"id":88},"por-que-um-arquivo-env-parece-privado-e-não-é","Por que um arquivo ",[14,91,16],{}," parece privado e não é",[10,94,95,96,98],{},"Porque o hábito de guardar uma chave em um arquivo ",[14,97,16],{}," vem dos servidores,\nonde funciona.",[10,100,101,102,104],{},"Em um servidor, o arquivo e o código que o lê ficam em uma máquina que você\ncontrola. Um visitante recebe uma resposta dessa máquina e nunca vê o arquivo.\nTirar a chave do código e colocá-la em um arquivo que o servidor lê ao\niniciar é boa prática lá, e foi lá que todo tutorial que diz «coloque no\n",[14,103,16],{},"» foi escrito.",[10,106,107,108,110,111,114,115,117,118,120],{},"O seu arquivo ",[14,109,16],{}," tem dois leitores, e os tutoriais falam de um deles. O\nprimeiro é qualquer um que consiga ver o seu projeto: um colaborador, um\nrepositório público no GitHub, a visualização de arquivos do próprio builder.\nUma entrada no ",[14,112,113],{},".gitignore",", que é uma lista de arquivos que o git deixa de\nfora, mantém o ",[14,116,16],{}," longe desse leitor. O segundo leitor é o build, que abre\no arquivo toda vez que você publica e copia tudo o que carrega o prefixo. O\n",[14,119,113],{}," não diz nada a ele.",[122,123],"diagram",{"alt":124,"caption":125,"src":126},"Um arquivo .env no meio com três valores, dois deles marcados VITE_. À esquerda, uma parede tracejada com a etiqueta .gitignore barra o caminho até uma pilha de arquivos de repositório. À direita, os dois valores marcados atravessam uma etapa de build até uma janela de navegador com uma pessoa ao lado, e o valor sem marca fica no arquivo.","Um arquivo, dois leitores. A parede do .gitignore detém um deles. O build leva cada valor com prefixo até o outro.","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Ftwo-readers-one-file-1600x760.png",[10,128,129,130,132],{},"Então «meu ",[14,131,16],{}," está no gitignore» é verdade, e responde a outra pergunta. O\narquivo ficou fora do seu repositório. Os valores com prefixo entraram no app\nmesmo assim, porque essa é a rota que o prefixo abre, e em um projeto do\nLovable, do Bolt ou do v0 a maior parte do código que você vem editando roda\nno navegador, onde não há servidor atrás do qual o arquivo possa ficar.",[44,134,136],{"id":135},"por-que-a-ia-recorreu-ao-prefixo","Por que a IA recorreu ao prefixo",[10,138,139],{},"Porque é assim que se faz um valor funcionar em código do navegador, e o\nmodelo não faz ideia de quanto esse valor custa.",[10,141,142,143,146,147,150],{},"Você pediu um mapa, ou um chat que responde perguntas sobre o seu produto. O\ncódigo que o builder escreveu para isso roda no navegador do visitante, e\ncódigo de navegador que lê ",[14,144,145],{},"process.env.OPENAI_API_KEY"," não recebe nada. O\njeito de fazer o valor chegar é o prefixo. O builder renomeia a variável para\n",[14,148,149],{},"VITE_OPENAI_API_KEY",", a função funciona na pré-visualização, e nenhum erro\naparece em lugar nenhum, porque do lado da ferramenta de build nada deu\nerrado.",[10,152,153],{},"O prefixo não julga. É a mesma instrução para uma chave do Google Maps, que\ndeve ser pública depois de restringida, e para uma chave secreta do Stripe,\nque pode reembolsar cada cliente que você tem. Uma pessoa que soubesse que a\nsegunda cobra o seu cartão pararia. O modelo sabe que a função não funcionava\naté a linha estar lá, e depois funcionou.",[10,155,156,157,159],{},"É por isso que isso aparece em apps cujos donos fizeram tudo o que mandaram.\nO valor foi tirado do código, guardado no ",[14,158,16],{},", o arquivo estava no\ngitignore, e o app o entrega mesmo assim, porque a única etapa que o publica\nparece a etapa que o faz funcionar.",[44,161,163],{"id":162},"quais-valores-vão-atrás-do-prefixo","Quais valores vão atrás do prefixo",[10,165,166],{},"Um endereço e uma chave publicável. Qualquer coisa que gaste dinheiro ou\nignore as regras do seu banco de dados, não.",[168,169,170,192],"table",{},[171,172,173],"thead",{},[174,175,176,180,189],"tr",{},[177,178,179],"th",{},"Valor",[177,181,182,183,185,186,188],{},"Atrás de ",[14,184,20],{}," ou ",[14,187,38],{},"?",[177,190,191],{},"Por quê",[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","Fica aqui",[198,210,211],{},"Um endereço. Diz com qual projeto o seu app conversa e nada mais.",[174,213,214,224,228],{},[198,215,216,219,220,223],{},[14,217,218],{},"VITE_SUPABASE_PUBLISHABLE_KEY",", ou ",[14,221,222],{},"VITE_SUPABASE_ANON_KEY"," em um projeto antigo",[198,225,226],{},[205,227,208],{"type":207},[198,229,230],{},"Desenhada para o navegador. Cada pedido que ela faz continua filtrado pelo Row Level Security, as regras de cada tabela que decidem linha a linha quem pode ler o quê.",[174,232,233,240,244],{},[198,234,235,236,239],{},"Uma chave ",[14,237,238],{},"pk_live_"," do Stripe",[198,241,242],{},[205,243,208],{"type":207},[198,245,246],{},"Monta formulários de pagamento. Não consegue cobrar, reembolsar nem ler clientes.",[174,248,249,252,257],{},[198,250,251],{},"Uma chave do Google Maps",[198,253,254],{},[205,255,256],{"type":207},"Fica aqui, depois de restringida",[198,258,259],{},"Pública por desenho. Uma restrição por referrer no Google Cloud é o que impede um estranho de gerar cobranças para você com ela.",[174,261,262,265,271],{},[198,263,264],{},"Uma chave da OpenAI ou da Anthropic",[198,266,267],{},[205,268,270],{"type":269},"danger","Nunca",[198,272,273],{},"Não existe variante publicável. Quem a tiver gasta o seu dinheiro.",[174,275,276,285,289],{},[198,277,235,278,185,281,284],{},[14,279,280],{},"service_role",[14,282,283],{},"sb_secret_"," do Supabase",[198,286,287],{},[205,288,270],{"type":269},[198,290,291],{},"Ignora o Row Level Security e lê cada linha de cada tabela.",[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],{},"Cobranças, reembolsos, repasses e cada registro de cliente.",[174,307,308,311,315],{},[198,309,310],{},"Uma chave de acesso da AWS",[198,312,313],{},[205,314,270],{"type":269},[198,316,317],{},"O que quer que essa conta consiga fazer, de qualquer lugar.",[10,319,320,321,323],{},"Se o seu app tem ",[14,322,62],{}," e uma chave publicável ao lado, esse é o\npar que o Supabase planejou para um navegador, e o nosso escaneamento o marca\ncomo no lugar certo. O endereço e a chave publicável são o motivo de o\nprefixo existir.",[10,325,326,327,332],{},"O teste para todo o resto é se você se importaria de ver o valor impresso na\nsua página inicial. O prefixo o deixa um clique mais longe do que isso, em um\narquivo em vez de na página, e quem quiser o arquivo o tem.\n",[328,329,331],"a",{"href":330},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","Distinguir as duas famílias",",\npelo prefixo e, em uma chave antiga do Supabase, pelo papel dentro dela, é um\nartigo à parte.",[44,334,336],{"id":335},"o-que-30998-apps-entregavam-atrás-do-prefixo","O que 30.998 apps entregavam atrás do prefixo",[10,338,339],{},"Principalmente chaves de API do Google. 52 apps entregavam uma chave que gasta\ndinheiro ou lê tudo.",[10,341,342,343,347,348,352,353,185,356,359],{},"Em agosto de 2026 rodamos\n",[328,344,346],{"href":345},"\u002Fresearch\u002Fvibe-coded-app-security-2026","as mesmas nove verificações"," em\n30.998 apps vibe-coded no ar. 1.332 delas, 4%, entregavam algo com cara de\nchave no código que cada visitante baixa. 1.142 dessas eram chaves de API do\nGoogle, que normalmente precisam de uma\n",[328,349,351],{"href":350},"\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend","restrição definida no Google Cloud","\ne não de rotação. 204 entregavam um valor de aparência aleatória ao lado de\num nome como ",[14,354,355],{},"secret",[14,357,358],{},"password",", que pode ser uma credencial real ou não.",[10,361,362,363,365,366,370],{},"As caras eram raras. 33 apps entregavam uma chave da OpenAI, 9 uma chave de\nacesso da AWS, 5 uma chave da Anthropic, 3 uma chave secreta do Stripe e 3\numa ",[14,364,280],{}," do Supabase: 52 apps ao todo, já que uma delas carregava\nduas. Então o valor atrás do prefixo normalmente é uma chave do Google, e a\nsolução para isso é uma configuração. O caso raro é onde está o dano, e\n",[328,367,369],{"href":368},"\u002Fblog\u002Fopenai-api-key-exposed-in-frontend","o que custa uma chave da OpenAI vazada","\né o artigo para esse.",[44,372,374],{"id":373},"como-verificar-o-que-o-seu-próprio-app-entrega","Como verificar o que o seu próprio app entrega",[10,376,377],{},"Abra o app no ar, aperte F12 e busque o valor em cada arquivo carregado.",[379,380,381,385,392,395],"ol",{},[382,383,384],"li",{},"Abra o seu app publicado em um navegador, no endereço real dele. A\npré-visualização do builder é outro build e pode estar uma versão atrás.",[382,386,387,388,391],{},"Aperte F12 para abrir as ferramentas de desenvolvedor e escolha a aba\n",[29,389,390],{},"Sources",".",[382,393,394],{},"Aperte Ctrl+Shift+F, ou Cmd+Option+F em um Mac. Isso abre uma busca em cada\narquivo que a página carregou.",[382,396,397],{},"Cole os primeiros dez caracteres, mais ou menos, do valor que preocupa\nvocê, e não o cole em nenhum outro lugar.",[10,399,400,401,404,405,407],{},"Um resultado significa que o valor está na caixa que cada visitante recebe.\nBusque o valor e não o nome: o build normalmente troca\n",[14,402,403],{},"import.meta.env.VITE_OPENAI_API_KEY"," pelo valor em si, então uma busca por\n",[14,406,20],{}," pode voltar vazia enquanto cada valor que havia atrás está lá.",[122,409],{"alt":410,"caption":411,"src":412},"Dois painéis. À esquerda, um arquivo-fonte com uma linha import.meta.env.VITE_OPENAI_API_KEY entre linhas de código lisas. À direita, depois de uma etapa de build, o mesmo arquivo comprimido em ruído denso, com o nome da variável sumido e o valor dela desenhado como uma barra destacada sob uma lupa.","O build troca o nome pelo valor. Buscar VITE_ no app no ar não encontra nada; buscar o valor encontra.","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fname-goes-value-stays-1600x640.png",[10,414,415,416,391],{},"Se você prefere não percorrer o seu app valor por valor, o nosso escaneamento\ngratuito lê o seu site no ar de fora, todas as nove verificações, em uns 20\nsegundos e sem conta. Ele nomeia cada chave que encontra pelo tipo, diz quais\nficam bem em um navegador, e escreve «Não foi possível verificar» para tudo o\nque não conseguiu responder em vez de um tique:\n",[328,417,419],{"href":418},"\u002Fsecurity-scanner","escaneie o seu app",[44,421,423],{"id":422},"para-onde-vai-um-segredo-de-verdade","Para onde vai um segredo de verdade",[10,425,426],{},"Para uma máquina da qual os seus visitantes nunca baixam nada. Em um projeto\ndo Supabase, é uma Edge Function, um pedacinho de código de servidor que o\nSupabase roda para você; em um app Next.js, é uma rota de servidor; em um app\ndo Replit, é a metade do servidor.",[10,428,429,430,433],{},"O formato é o mesmo em todo lugar. O seu código do navegador pede à sua\nfunção que faça o trabalho. A função guarda a chave, faz a chamada à OpenAI\nou ao Stripe e devolve a resposta. A chave fica na máquina e o visitante\nrecebe um resultado. O\n",[328,431,432],{"href":368},"artigo sobre a OpenAI"," desenha isso\ncomo um objeto se movendo uma caixa para a direita, e a mudança é só isso.",[10,435,436,437,440,441,443,444,185,447,450,451,391],{},"Duas coisas dizem que o builder fez o que você pediu. A variável perdeu o\nprefixo, então é ",[14,438,439],{},"OPENAI_API_KEY",", e vive nos secrets da própria função,\ndefinidos no painel do Supabase em Edge Functions, sem nada no ",[14,442,16],{}," do app.\nE o arquivo que a lê fica em ",[14,445,446],{},"supabase\u002Ffunctions\u002F",[14,448,449],{},"app\u002Fapi\u002F",", em algum\nlugar que o build nunca empacota, em vez de em ",[14,452,453],{},"src\u002F",[10,455,456,457,461],{},"Uma ordem importa, e é fácil invertê-la. Se uma chave secreta já foi entregue\natrás de um prefixo, movê-la não faz nada pelas cópias já baixadas. Rotacione\nprimeiro no provedor, depois mova o trabalho.\n",[328,458,460],{"href":459},"\u002Fblog\u002Frotate-supabase-service-role-key","Se rotacionar primeiro ou fechar o vazamento primeiro","\ndepende de a cópia já ser pública, e uma vez que ela esteve em um bundle\npublicado, é.",[10,463,464,465,469],{},"Em um projeto do Replit a mesma separação tem nome próprio, Secrets, e\n",[328,466,468],{"href":467},"\u002Fblog\u002Freplit-secrets-explained","o que a ferramenta Secrets cobre e o que não cobre","\né um artigo à parte.",[44,471,473],{"id":472},"o-que-fazer-agora","O que fazer agora",[475,476,477],"key-takeaways",{},[478,479,480,493,505,508,511,514],"ul",{},[382,481,482,483,485,486,485,488,35,490,492],{},"Busque no seu projeto ",[14,484,20],{},", ",[14,487,38],{},[14,489,80],{},[14,491,84],{},". Cada ocorrência é um valor que o seu build publica de propósito. Decida para cada um se pode ser público.",[382,494,495,496,35,498,500,501,504],{},"Mantenha o endereço e a chave publicável. ",[14,497,62],{},[14,499,218],{},", ou a chave ",[14,502,503],{},"anon"," em um projeto antigo, são o par para o qual o prefixo existe.",[382,506,507],{},"Uma chave secreta atrás de um prefixo é rotacionada primeiro no provedor, depois movida. Apagar a linha não recolhe uma cópia que já foi baixada.",[382,509,510],{},"Mova o trabalho que precisava da chave para uma Edge Function ou uma rota de servidor, com a chave nos secrets dessa função e sem prefixo no nome.",[382,512,513],{},"Restrinja uma chave do Google por referrer no Google Cloud. Essa precisa de uma configuração e mantém o valor.",[382,515,516],{},"Depois da próxima publicação, busque no app no ar cada valor que você moveu.",[10,518,519],{},"Cada publicação empacota uma caixa nova. A próxima função que você pedir é\nmais uma chance de um valor receber o prefixo, e nada entre o builder e a\ninternet lê o bundle no caminho de saída. Um escaneamento do mês passado leu\no bundle do mês passado.",[10,521,522,523,527],{},"O ",[328,524,526],{"href":525},"\u002Fpricing","Reeve Monitor"," lê o bundle por você. Ele roda de novo todas as\nnove verificações a cada hora em até três apps, avisa você quando um\nresultado muda, vigia a disponibilidade a cada 60 segundos e envia um\nrelatório mensal. Uma chave que chega ao bundle com a publicação de terça\naparece no reescaneamento daquela hora, quer você tenha lembrado de olhar ou\nnão. Custa $12 por mês no preço de lista, com sete dias grátis antes de\ncobrar; a página de preços às vezes fica abaixo do valor daqui e nunca acima.",[10,529,530,531,535],{},"Se você prefere seguir isso como uma lista, a\n",[328,532,534],{"href":533},"\u002Fchecklist","checklist de segurança de 10 minutos"," cobre isto e as outras\ncoisas que vale a pena desligar em um app recém-lançado.",{"title":537,"searchDepth":538,"depth":538,"links":539},"",3,[540,542,544,545,546,547,548,549],{"id":46,"depth":541,"text":47},2,{"id":88,"depth":541,"text":543},"Por que um arquivo .env parece privado e não é",{"id":135,"depth":541,"text":136},{"id":162,"depth":541,"text":163},{"id":335,"depth":541,"text":336},{"id":373,"depth":541,"text":374},{"id":422,"depth":541,"text":423},{"id":472,"depth":541,"text":473},"Noções de segurança","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fcover-1200x630.png","Um arquivo .env com três valores, dois com a etiqueta VITE_, e esses dois valores desenhados de novo numa janela de navegador ao lado.","Variáveis de ambiente do Vite expostas no seu app: fizeram o que o prefixo pediu. VITE_ e NEXT_PUBLIC_ significam publique isto, e a IA nunca soube o custo.",false,"md",[557,560,563,566,569,572],{"q":558,"a":559},"Arquivos .env são secretos?","Para o seu repositório, sim, se o arquivo estiver no .gitignore. Para os seus visitantes, não. O build lê o .env toda vez que você publica e copia cada valor com o prefixo VITE_ ou NEXT_PUBLIC_ para o JavaScript que o seu app entrega. O arquivo em si nunca sai da sua máquina; os valores que você etiquetou para o navegador, sim.",{"q":561,"a":562},"É seguro expor VITE_SUPABASE_ANON_KEY?","Ela foi feita para estar lá. A chave anon, chamada de chave publicável em um projeto novo, foi desenhada para viver em um navegador. Ela só diz a que projeto um pedido pertence, e cada pedido que ela faz passa pelo filtro das suas regras de Row Level Security. Isso vale exatamente enquanto essas regras estiverem ligadas e corretas, o que é outra coisa a verificar.",{"q":564,"a":565},"NEXT_PUBLIC_ é diferente de VITE_?","Mesma regra, outra ferramenta de build. O Next.js escreve o valor de qualquer variável NEXT_PUBLIC_ no bundle do navegador como uma string fixa na hora do build, e uma variável sem o prefixo chega vazia ao código do navegador. O Expo faz o mesmo com EXPO_PUBLIC_, e projetos antigos do Create React App com REACT_APP_. Seja qual for a ferramenta que construiu o seu app, o prefixo significa publicar.",{"q":567,"a":568},"Como verifico o que está no meu bundle?","Abra o app no ar, aperte F12, escolha Sources e aperte Ctrl+Shift+F (Cmd+Option+F em um Mac) para buscar em cada arquivo que a página carregou. Cole os primeiros caracteres do valor. Busque o valor e não o nome da variável, porque o build normalmente troca o nome pelo valor, então VITE_ pode não aparecer enquanto a chave está lá. Nosso escaneamento gratuito faz a mesma leitura de fora em uns 20 segundos.",{"q":570,"a":571},"Onde uma chave secreta deveria viver em um app do Lovable ou do Bolt?","Em uma Supabase Edge Function, com a chave definida nos secrets dessa função no painel do Supabase e sem o prefixo VITE_ no nome. O seu código do navegador chama a função, a função chama o provedor com a chave, e a chave nunca chega a um visitante. Se a chave já foi entregue, rotacione-a no provedor antes de movê-la.",{"q":573,"a":574},"O .gitignore protege as minhas chaves?","Ele mantém o arquivo .env fora do git, então ninguém que leia o seu repositório o vê. Não tem efeito nenhum sobre o build, que lê o arquivo diretamente e publica cada valor com prefixo. Um .env ignorado pelo git com VITE_OPENAI_API_KEY dentro continua entregando essa chave a cada visitante.","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fcard-800x500.png",[577,578,579,580,581,582,583,584,585],"variáveis de ambiente vite expostas","prefixo VITE_ segurança","next_public_ variável de ambiente secreta","variáveis .env são seguras no react","vite_ é seguro","variáveis de ambiente no frontend","arquivo .env exposto no navegador","expo_public env","esconder api key no frontend",{},true,"Variáveis de ambiente do Vite expostas: VITE_ diz publique isto","\u002Fblog\u002Fvite-and-next-public-env-vars","2026-09-18",{"title":5,"description":553},"blog\u002Fvite-and-next-public-env-vars",[594,595,596],"As variáveis de ambiente do Vite expostas no seu app estão lá porque o prefixo pediu. Uma variável chamada VITE_ ou NEXT_PUBLIC_ é copiada para o JavaScript que cada visitante baixa, e guardá-la em um arquivo .env não impede nada disso.","O prefixo é a etiqueta certa para um endereço ou uma chave publicável, e a errada para qualquer coisa que gaste dinheiro ou ignore as regras do seu banco de dados. O build não distingue as duas, e a IA que escreveu a linha também não.","De 30.998 apps vibe-coded no ar que escaneamos, 1.332 entregavam algo com cara de chave. 1.142 delas eram chaves de API do Google, que normalmente precisam de uma restrição e não de rotação. 52 entregavam uma chave que gasta dinheiro ou lê tudo.","UCDIReGteFJe2O-dpU7r1iF8w8D4Kem2JiDsup3lIa0",[599,606,612,618,624,630,631,637,643,649,655,661,667,673,679,685,690,696,702,708,714,719,725,731,736,742,748,754,760,766,772,778,784,789,795,801,806,812,818,824,829,835],{"path":600,"title":601,"description":602,"published":603,"category":604,"image":605,"draft":554},"\u002Fblog\u002Fsupabase-backup-auth-users","Por que seu dump do Supabase não tem usuários dentro","Rode supabase db dump sozinho e você recebe a forma do seu banco e nenhuma das linhas dele, sem o schema auth onde seus usuários moram.","2026-09-23","Backups","\u002Fblog\u002Fsupabase-backup-auth-users\u002Fcard-800x500.png",{"path":607,"title":608,"description":609,"published":610,"category":550,"image":611,"draft":554},"\u002Fblog\u002Fdomain-and-certificate-expiry","Domínio expirado, site fora do ar: o que acontece agora","Seu domínio expirou e seu site está fora do ar. O relógio que começa a correr, por que o certificado é o problema menor, e como conferir os dois.","2026-09-22","\u002Fblog\u002Fdomain-and-certificate-expiry\u002Fcard-800x500.png",{"path":613,"title":614,"description":615,"published":616,"category":550,"image":617,"draft":554},"\u002Fblog\u002Fis-lovable-safe","O Lovable é seguro? O que vimos em 18.554 aplicativos Lovable","O Lovable é seguro? Passamos 18.554 aplicativos Lovable no ar por nove verificações. A plataforma foi a mais limpa de cinco. Os achados estavam nos aplicativos.","2026-09-21","\u002Fblog\u002Fis-lovable-safe\u002Fcard-800x500.png",{"path":619,"title":620,"description":621,"published":622,"category":550,"image":623,"draft":554},"\u002Fblog\u002Fvibe-coded-app-security-checklist","O checklist de segurança do vibe coding, em nove verificações","Um checklist de segurança do vibe coding com nove itens, cada um verificável no seu app de fora por qualquer pessoa, e cada um com um teste de uma linha.","2026-09-20","\u002Fblog\u002Fvibe-coded-app-security-checklist\u002Fcard-800x500.png",{"path":625,"title":626,"description":627,"published":628,"category":550,"image":629,"draft":554},"\u002Fblog\u002Fstripe-secret-key-in-frontend","Uma chave secreta da Stripe no frontend pode mover dinheiro","Uma chave secreta da Stripe exposta no seu frontend pode reembolsar, cobrar e ler cada ficha de cliente que você tem. Sua chave pk_live_ é que deve estar aí.","2026-09-19","\u002Fblog\u002Fstripe-secret-key-in-frontend\u002Fcard-800x500.png",{"path":589,"title":5,"description":553,"published":590,"category":550,"image":575,"draft":554},{"path":632,"title":633,"description":634,"published":635,"category":550,"image":636,"draft":554},"\u002Fblog\u002Fbase44-source-maps","Segurança no Base44: o que um scan aponta e o que é seu para corrigir","Segurança no Base44 em 5.438 apps escaneados: três achados em quase todos são da plataforma, o source map é o selo do Base44, e a sua parte é uma lista curta.","2026-09-17","\u002Fblog\u002Fbase44-source-maps\u002Fcard-800x500.png",{"path":638,"title":639,"description":640,"published":641,"category":550,"image":642,"draft":554},"\u002Fblog\u002Fis-cursor-ai-safe","O Cursor AI é seguro? O editor, o código e o app que você publicou","O Cursor AI é seguro? Três perguntas em uma busca: o que o Cursor guarda, o que o código que ele escreve erra, e se o app que você publicou está aberto.","2026-09-16","\u002Fblog\u002Fis-cursor-ai-safe\u002Fcard-800x500.png",{"path":644,"title":645,"description":646,"published":647,"category":550,"image":648,"draft":554},"\u002Fblog\u002Fis-replit-safe","O Replit é seguro? O que vimos em 3.042 aplicativos Replit no ar","O Replit é seguro? Passamos 3.042 aplicativos Replit no ar por nove verificações externas. Os achados estavam no aplicativo publicado, não no host.","2026-09-15","\u002Fblog\u002Fis-replit-safe\u002Fcard-800x500.png",{"path":650,"title":651,"description":652,"published":653,"category":604,"image":654,"draft":554},"\u002Fblog\u002Fsupabase-storage-backup","Supabase storage backup: sua cópia do banco não tem nenhum arquivo","Um Supabase storage backup é uma tarefa à parte. Os backups do banco guardam a lista dos seus arquivos e nenhum deles, então cada upload fica quebrado.","2026-09-14","\u002Fblog\u002Fsupabase-storage-backup\u002Fcard-800x500.png",{"path":656,"title":657,"description":658,"published":659,"category":604,"image":660,"draft":554},"\u002Fblog\u002Fsupabase-point-in-time-recovery","Point-in-time recovery do Supabase: preço e limites","O point-in-time recovery do Supabase rebobina seu banco para qualquer segundo da última semana. Custa $100 por mês além do Pro e cobre só o banco.","2026-09-13","\u002Fblog\u002Fsupabase-point-in-time-recovery\u002Fcard-800x500.png",{"path":662,"title":663,"description":664,"published":665,"category":604,"image":666,"draft":554},"\u002Fblog\u002Fsupabase-project-paused-recover","Projeto Supabase pausado? Seus dados continuam lá","O Supabase pausou seu projeto depois de uma semana sem atividade. Nada foi apagado, Restaurar fica ao lado do nome e você tem um ano antes disso mudar.","2026-09-12","\u002Fblog\u002Fsupabase-project-paused-recover\u002Fcard-800x500.png",{"path":668,"title":669,"description":670,"published":671,"category":550,"image":672,"draft":554},"\u002Fblog\u002Fsafest-ai-app-builder","Qual builder de apps com IA é mais seguro? 30.998 apps testados","Qual builder de apps com IA é mais seguro? Escaneamos 30.998 apps no ar do Lovable, Base44, Replit, v0 e Bolt. O builder não é o que decide sua nota.","2026-09-11","\u002Fblog\u002Fsafest-ai-app-builder\u002Fcard-800x500.png",{"path":674,"title":675,"description":676,"published":677,"category":550,"image":678,"draft":554},"\u002Fblog\u002Fenable-rls-on-every-supabase-table","Ative Row Level Security em toda tabela do Supabase e comprove","Ativar Row Level Security no Supabase sem política fecha a tabela por completo. Uma política sem a chave ligada não faz nada. Aqui está o SQL, e o teste.","2026-09-10","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Fcard-800x500.png",{"path":680,"title":681,"description":682,"published":683,"category":550,"image":684,"draft":554},"\u002Fblog\u002Fsupabase-rls-disabled-in-public","Supabase \"RLS disabled in public\": o que o aviso não vê","O Supabase marca \"RLS disabled in public\" como erro. Ele não diz nada sobre a política de leitura que deixa a sua tabela igualmente aberta.","2026-09-09","\u002Fblog\u002Fsupabase-rls-disabled-in-public\u002Fcard-800x500.png",{"path":459,"title":686,"description":687,"published":688,"category":550,"image":689,"draft":554},"Como rotacionar uma chave service_role do Supabase vazada","O Supabase manda fechar o vazamento primeiro. Outros guias mandam rotacionar agora. O que vale depende de para onde a sua chave service_role vazou.","2026-09-08","\u002Fblog\u002Frotate-supabase-service-role-key\u002Fcard-800x500.png",{"path":691,"title":692,"description":693,"published":694,"category":550,"image":695,"draft":554},"\u002Fblog\u002Fvibe-coding-security-scanners-compared","Scanners de segurança para vibe coding comparados, o nosso incluído","O melhor scanner de segurança para vibe coding depende de três perguntas que nenhuma lista de recursos responde. Dez ferramentas comparadas, com preços.","2026-09-07","\u002Fblog\u002Fvibe-coding-security-scanners-compared\u002Fcard-800x500.png",{"path":697,"title":698,"description":699,"published":700,"category":550,"image":701,"draft":554},"\u002Fblog\u002Fsupabase-security-checker","Supabase security checker: faça você mesmo as cinco verificações","Um Supabase security checker lê o seu app publicado em vez das configurações do projeto. Aqui estão as cinco verificações e como fazer cada uma sozinho.","2026-09-06","\u002Fblog\u002Fsupabase-security-checker\u002Fcard-800x500.png",{"path":703,"title":704,"description":705,"published":706,"category":550,"image":707,"draft":554},"\u002Fblog\u002Fvibe-coding-security-scanner","Scanner de segurança para vibe coding: o que ele não vê","Um scanner de segurança para vibe coding lê seu app no ar, de fora. O que isso cobre, as quatro coisas que ele não consegue ver e como ler o resultado.","2026-09-05","\u002Fblog\u002Fvibe-coding-security-scanner\u002Fcard-800x500.png",{"path":709,"title":710,"description":711,"published":712,"category":550,"image":713,"draft":554},"\u002Fblog\u002Fmissing-security-headers","Cabeçalhos de segurança faltando: quando isso importa","Cabeçalhos de segurança faltando é o achado que nosso scanner mais imprime. Contra o que eles protegem e quando são a linha menos urgente do relatório.","2026-09-04","\u002Fblog\u002Fmissing-security-headers\u002Fcard-800x500.png",{"path":368,"title":715,"description":716,"published":717,"category":550,"image":718,"draft":554},"Sua chave de API da OpenAI está exposta no frontend. Troque-a.","Uma chave de API da OpenAI exposta no frontend não pode ser presa a um domínio. Troque-a hoje, mova a chamada para o seu servidor e limite o gasto.","2026-09-03","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":720,"title":721,"description":722,"published":723,"category":550,"image":724,"draft":554},"\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps","Chave de API exposta no frontend: o que 30.998 apps entregam","Uma chave de API exposta no seu frontend costuma ser uma chave do Google Maps. Escaneamos 30.998 apps vibe-coded e contamos quais segredos vazam de verdade.","2026-09-02","\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps\u002Fcard-800x500.png",{"path":726,"title":727,"description":728,"published":729,"category":604,"image":730,"draft":554},"\u002Fblog\u002Fsupabase-backup-tools-compared","Ferramentas de backup do Supabase comparadas, a nossa incluída","Quatro tipos de ferramenta de backup do Supabase, o que cada uma copia de verdade e quando uma GitHub Action gratuita ganha de pagar, nós inclusive.","2026-09-01","\u002Fblog\u002Fsupabase-backup-tools-compared\u002Fcard-800x500.png",{"path":467,"title":732,"description":733,"published":734,"category":550,"image":735,"draft":554},"Como usar secrets no Replit, e o que ainda assim é publicado","Como usar secrets no Replit: adicionar um, ler de volta e resolver as duas causas do undefined. E as chaves que a ferramenta Secrets não consegue proteger.","2026-08-31","\u002Fblog\u002Freplit-secrets-explained\u002Fcard-800x500.png",{"path":737,"title":738,"description":739,"published":740,"category":604,"image":741,"draft":554},"\u002Fblog\u002Fback-up-supabase-free-tier","Backups do plano gratuito do Supabase: como fazer sem terminal","No plano gratuito do Supabase não existe backup nenhum: a cópia é você que faz. Como fazer uma pelo painel e o que a rota do CSV deixa para trás.","2026-08-30","\u002Fblog\u002Fback-up-supabase-free-tier\u002Fcard-800x500.png",{"path":743,"title":744,"description":745,"published":746,"category":550,"image":747,"draft":554},"\u002Fblog\u002Fis-supabase-secure","O Supabase é seguro? É. Seu projeto é outra pergunta","O Supabase é seguro? A plataforma é auditada, criptografada e passa por pentests. A documentação deles diz onde isso termina e começam suas configurações.","2026-08-29","\u002Fblog\u002Fis-supabase-secure\u002Fcard-800x500.png",{"path":749,"title":750,"description":751,"published":752,"category":550,"image":753,"draft":554},"\u002Fblog\u002Fwhere-to-find-supabase-api-keys","Onde ficam as chaves de API do Supabase: anon, service_role e URL","A URL do projeto, a chave anon e a chave service_role ficam todas numa página do painel. Aqui está onde ela fica, e qual dos quatro valores vai no seu app.","2026-08-28","\u002Fblog\u002Fwhere-to-find-supabase-api-keys\u002Fcard-800x500.png",{"path":755,"title":756,"description":757,"published":758,"category":550,"image":759,"draft":554},"\u002Fblog\u002Fnew-row-violates-row-level-security-policy","New row violates row-level security policy no Supabase. E agora?","\"New row violates row-level security policy\" significa que o Supabase recusou uma escrita. O conserto rápido reabre a tabela para qualquer um.","2026-08-27","\u002Fblog\u002Fnew-row-violates-row-level-security-policy\u002Fcard-800x500.png",{"path":761,"title":762,"description":763,"published":764,"category":550,"image":765,"draft":554},"\u002Fblog\u002Fcors-wildcard-security-risk","Um curinga CORS é um risco de segurança? Quase nunca.","Um curinga CORS é um risco de segurança? Em geral é o padrão do seu builder e não entrega nada que o seu servidor já não desse a qualquer um que pedisse.","2026-08-26","\u002Fblog\u002Fcors-wildcard-security-risk\u002Fcard-800x500.png",{"path":767,"title":768,"description":769,"published":770,"category":604,"image":771,"draft":554},"\u002Fblog\u002Fsupabase-branching-is-not-a-backup","Branching do Supabase não é backup. Ele só anda para a frente.","Por que o branching do Supabase não é backup: um branch começa sem os seus dados e um merge só leva o esquema. Para que ele serve e o que usar no lugar.","2026-08-25","\u002Fblog\u002Fsupabase-branching-is-not-a-backup\u002Fcard-800x500.png",{"path":773,"title":774,"description":775,"published":776,"category":604,"image":777,"draft":554},"\u002Fblog\u002Fhow-to-restore-a-supabase-backup","Como restaurar um backup do Supabase, e o que quebra depois","Como restaurar um backup do Supabase pelo painel ou por um arquivo de dump, o que a restauração substitui e por que o seu app pode continuar quebrado no fim.","2026-08-24","\u002Fblog\u002Fhow-to-restore-a-supabase-backup\u002Fcard-800x500.png",{"path":779,"title":780,"description":781,"published":782,"category":550,"image":783,"draft":554},"\u002Fblog\u002Fsupabase-storage-bucket-public","Seu bucket do Supabase Storage é público. Isso é um problema?","Um bucket público do Supabase Storage significa que quem tem a URL de um arquivo pode abri-lo. Não significa que alguém possa listar o que está lá dentro.","2026-08-23","\u002Fblog\u002Fsupabase-storage-bucket-public\u002Fcard-800x500.png",{"path":350,"title":785,"description":786,"published":787,"category":550,"image":788,"draft":554},"Uma chave de API do Google no frontend é um problema?","Uma chave de API do Google exposta no seu frontend é a chave que nosso scanner mais encontra, e quase sempre está tudo bem. Um ajuste gratuito decide isso.","2026-08-22","\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":790,"title":791,"description":792,"published":793,"category":604,"image":794,"draft":554},"\u002Fblog\u002Fai-agent-deleted-my-database","Um agente de IA apagou meus dados do Supabase. O que eu recupero?","Um agente de IA apagou dados do seu banco. O que você recupera já estava decidido antes de ele rodar, e os próximos minutos decidem o resto.","2026-08-21","\u002Fblog\u002Fai-agent-deleted-my-database\u002Fcard-800x500.png",{"path":796,"title":797,"description":798,"published":799,"category":550,"image":800,"draft":554},"\u002Fblog\u002Fcan-anyone-read-your-supabase-database","Qualquer um consegue ler seu banco Supabase? Testamos 3.680 apps","Qualquer um consegue ler seu banco de dados Supabase sem fazer login? Escaneamos 30.998 apps no ar criadas com builders de IA e medimos quantas dizem que sim.","2026-08-18","\u002Fblog\u002Fcan-anyone-read-your-supabase-database\u002Fcard-800x500.png",{"path":802,"title":803,"description":804,"published":799,"category":550,"image":805,"draft":554},"\u002Fblog\u002Fsource-maps-exposed-in-production","Source maps expostos: seu app está publicando o código original","Um source map exposto deixa qualquer um ler o código original do seu app, comentários incluídos. A verificação de 30 segundos e o que importa de verdade.","\u002Fblog\u002Fsource-maps-exposed-in-production\u002Fcard-800x500.png",{"path":807,"title":808,"description":809,"published":810,"category":550,"image":811,"draft":554},"\u002Fblog\u002Fsupabase-new-api-keys","As novas chaves de API do Supabase: qual vai no seu app?","O Supabase trocou anon e service_role por chaves publicáveis e secretas. Qual delas pertence ao seu app e qual nunca deve estar lá?","2026-08-12","\u002Fblog\u002Fsupabase-new-api-keys\u002Fcard-800x500.png",{"path":813,"title":814,"description":815,"published":816,"category":604,"image":817,"draft":554},"\u002Fblog\u002Fdoes-supabase-back-up-my-database","O Supabase faz backup do meu banco de dados? Depende do plano.","O Supabase faz backup do seu banco de dados? Todo dia nos planos pagos, e nada no gratuito. Como descobrir o que você tem e o que essa cópia não cobre.","2026-08-11","\u002Fblog\u002Fdoes-supabase-back-up-my-database\u002Fcard-800x500.png",{"path":819,"title":820,"description":821,"published":822,"category":550,"image":823,"draft":554},"\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","O Supabase Row Level Security está ligado. A tabela continua pública.","Ligar o Supabase Row Level Security não protege uma tabela. Quem protege são as políticas, e a que consertou o seu app pode deixar qualquer um entrar.","2026-08-10","\u002Fblog\u002Fsupabase-rls-on-but-table-still-public\u002Fcard-800x500.png",{"path":825,"title":826,"description":827,"published":822,"category":604,"image":828,"draft":554},"\u002Fblog\u002Fversion-history-is-not-a-backup","O histórico de versões não é backup. Ele não desfaz um delete.","Lovable e Bolt guardam um histórico de versões do seu código. Seu banco de dados é um serviço à parte, e voltar atrás não traz os seus dados de volta.","\u002Fblog\u002Fversion-history-is-not-a-backup\u002Fcard-800x500.png",{"path":830,"title":831,"description":832,"published":833,"category":604,"image":834,"draft":554},"\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database","Três formas de fazer backup de um banco Supabase, e o que falta","O painel, o pg_dump e um serviço gerenciado. O que cada um salva de verdade, o que deixa de fora sem avisar e qual sobrevive a perder a conta.","2026-08-09","\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database\u002Fcard-800x500.png",{"path":330,"title":836,"description":837,"published":833,"category":550,"image":838,"draft":554},"Quais chaves de API são seguras no navegador e quais não são","Sua chave anon do Supabase foi feita para ser pública. Sua chave service_role não, e ela ignora todas as regras que você criar. Como diferenciar as duas.","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend\u002Fcard-800x500.png",1790150954259]