A secret is a key that grants a power: charging a card, sending emails on behalf of your domain, reading your whole database, calling a pay-per-use AI model. As long as it stays on a server, it does its job quietly. As soon as it shows up anywhere else, whoever reads it inherits the same power.
The three places where a secret escapes
Code sent to the browser. Everything the interface runs is downloaded by every
visitor, in plain text. The documentation for Vite, the build tool used by Lovable, Bolt
and many others, puts it plainly: variables prefixed with VITE_ are bundled into the
code sent to the client and should not contain sensitive information such as API keys.
Next.js applies the same logic to the NEXT_PUBLIC_ prefix.
The Git repository. A versioned .env file, a key hard-coded in a configuration
file or a test script. Deleting the line is not enough: the history keeps every version.
GitHub blocks pushes of many secret types to public repositories by default, through
push protection, but that net does not cover every format or every private repository.
The side channels. A screenshot shared in a ticket, an error log that prints the configuration, a prompt pasted into a third-party tool, a file sent to a freelancer. These leaks are less spectacular and harder to trace.
Why it is common in AI-built apps
The AI tool aims to make the request work. If you ask it to "send a welcome email with Resend" or "add an OpenAI summary", the shortest route is to call the service directly from the interface, with the key close at hand. The feature works, the demo convinces, and the key travels with every page.
Platforms have improved. Lovable offers a deep scan that looks for leaked secrets, among other things, and steers sensitive calls towards server functions. These safeguards are valuable. They do not replace a complete inventory, especially on a project that has gone through several tools, several exports or several people.
How to check your app
- Search for known prefixes in the browser code (developer tools, Sources tab) and
in the repository:
sk_live_andrk_live_(Stripe),sb_secret_orservice_role(Supabase),re_(Resend),sk-(OpenAI and others),AKIA(AWS) - List the
VITE_orNEXT_PUBLIC_variables and ask for each one: is this a value any visitor can know without risk? - Comb through the Git history with a dedicated tool such as gitleaks or trufflehog, which recognise hundreds of formats
- Check the
.gitignorefile:.env,.env.localand*.localshould be listed - Look at the logs and the error messages shown to users: they must never contain configuration
A concrete example
A note-taking tool built with v0 offers an AI summary of each note. The model call goes
straight from the browser, with the provider key in a NEXT_PUBLIC_ variable. Nobody
notices for three months. Then the monthly bill jumps from 40 to several thousand euros:
the key has been extracted and is powering other projects.
The fix has three steps: a new key with the provider, the call moved to a server function that checks the user is signed in, and a spending cap set with the provider. The old key is revoked last, once the new one is in place, to avoid any downtime.
The most common mistakes
- Assuming a private repository protects secrets: it is shared with every tool, collaborator and integration that has access
- Removing the key without replacing it, leaving the old one active
- Mixing up public and secret keys from the same provider (Stripe
pk_andsk_, Supabase publishable and secret) - Using the same key in test and production, which multiplies the places it travels
- Setting no cap on spend or volume with pay-per-use providers
The takeaway
An exposed secret is very fixable, as long as you handle it in order: new key, move it server-side, revoke the old one, then clean up. These steps are part of the pre-launch checklist. The vibebusters Scan looks for secrets in the served code, the repository and its history, and delivers a key-by-key replacement plan that the Production Upgrade can carry out for you.