Who sees this
You keep your app's code on GitHub, often through your AI tool's GitHub sync (Lovable, Bolt, Replit, Cursor or v0). A .env file with your keys went into a commit. Sometimes the AI tool did it for you. Then GitHub sends a warning email, Stripe or OpenAI emails you about an exposed key, or you spot the file yourself.
The common questions are "the repo is private, does it still count as leaked?" and "I deleted the file, so is it gone?". The answers are below.
First, check what is in the file
Not every value in a .env file is a secret. Open the file on GitHub and sort each line into one of two groups.
Meant to be public (no action needed):
- Values that start with
VITE_orNEXT_PUBLIC_. Your build copies these into the code every visitor downloads, so they are public anyway. - Your Supabase project URL and the anon key or publishable key (
sb_publishable_...). Supabase says the publishable key can be public, including in source code. What decides who can read and change your data is Row Level Security, not this key. See the Supabase anon key in the browser
Real secrets (rotate them):
- Stripe secret keys (
sk_live_...,sk_test_...) and restricted keys (rk_...), and webhook signing secrets (whsec_...) - A Supabase secret key (
sb_secret_...) or the legacyservice_rolekey - AI keys (OpenAI
sk-..., Anthropic and others), email keys (Resend and others) - Database connection strings with a password, SMTP passwords, anything named
SECRET,PRIVATEorPASSWORD
If the file only has public values, you don't need to rotate anything.
If you built with Lovable
Lovable puts a .env file in your project on purpose. It holds VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY. Lovable's docs say these can be public, and that you should not add .env to .gitignore in a Lovable project, because the preview and published builds need it. If you delete it, the preview breaks. Real secrets in Lovable go in Secrets, not in .env, and Edge Functions read them from there.
So in a Lovable project: keep .env, check that it only has VITE_ values, and move anything else to Secrets (then rotate it, see below).
Does a private repository change anything?
Not much. Treat a real secret in a private repository as leaked too. Stripe's advice is to rotate a key that was exposed "even if you aren't sure anyone saw it". OpenAI's advice says keys in a private repository can still leak in a data breach. Anyone you invited, any app you connected to the repository, and every copy someone downloaded can see the file. Rotating a key takes a few minutes and ends the question.
If the repository is public, act now. GitHub scans public repositories for known key formats and tells some providers, which may turn the key off for you. Don't wait for that: providers say they can't promise to catch every leak.
How to fix it, step by step
1. Rotate every real secret, starting with the most dangerous
Do this first. GitHub's own docs say that once a secret is rotated it can no longer be used, and that this may be all you need. Do the Stripe secret key and the Supabase secret key first, because they give full access to your payments and your data.
- Stripe: open the API keys page in the Stripe Dashboard. Rotate the secret key, or create a new one and then expire the old one. For a webhook signing secret, open that webhook endpoint in the Dashboard and roll its signing secret.
- Supabase: Settings → API Keys. Create a new secret key, put it everywhere your app uses the old one, then delete the old secret key. If the leaked key was the legacy
service_rolekey, deactivate the legacy keys, and also revoke the legacy JWT secret, as Supabase's guide "Rotate a leaked or compromised API key" explains. Your browser code then needs the new publishable key in place of the old anon key. - OpenAI and other AI tools: open the API keys page, create a new key, then delete the old one. Set a monthly spending limit so a leaked key can't run up a large bill.
- Database passwords: change the password in your database provider and update the connection string.
2. Put the new keys in the right place
The new keys must not go back into a file in your repository.
- Lovable: Cloud or Supabase Secrets for backend keys (Stripe, Resend, OpenAI).
- Supabase Edge Functions: Edge Functions → Secrets in the dashboard, or
supabase secrets set NAME=value. - Vercel, Netlify, Replit, Railway: the project's environment variables or Secrets page. Redeploy afterwards so the app reads the new values.
Check that the app still works: sign up, log in and open the payment page.
3. Stop Git tracking the file
Adding .env to .gitignore alone doesn't work if Git already tracks the file. Git's own docs say you have to remove it from the index first:
git rm --cached .env
echo ".env" >> .gitignore
git add .gitignore
git commit -m "Stop tracking .env"
git pushThis keeps the file on your computer and removes it from the next commits. Add a .env.example file with the variable names and no values, so others know which ones to set.
Skip this step in a Lovable project if the file only has VITE_ values (see above).
4. Decide whether to clean the history
The old commits still contain the file. Once every real key in it is rotated, the old values don't work, so many people stop here. GitHub's docs agree this can be enough.
If the file also held things you can't rotate (personal data, a customer list), or you want it gone anyway, GitHub explains how to rewrite the history with git-filter-repo (version 2.47 or later, with --sensitive-data-removal). Know the limits before you start:
- Anyone who already cloned or forked the repository keeps a copy.
- Old pull requests lose their diffs, and everyone working on the repository has to re-clone or rebase.
- GitHub still shows cached views of old commits until you contact GitHub Support and they remove them.
5. Stop it happening again
- Keep
.envin.gitignorefrom the first commit (except in Lovable, where.envholds only publicVITE_values). - Don't use
git add .without looking. Check what you are about to commit withgit statusorgit diff --cached. - GitHub's push protection blocks known key formats when you push to a public repository. It is on by default for your account. Leave it on, and don't bypass the warning.
- Tell your AI tool where secrets go (see the prompt below).
Fix prompt for your AI builder
Paste this into Lovable, Bolt, Cursor, Replit or whichever tool you use. Rotate the keys yourself first. Never paste a real key into the chat.
My .env file was committed to GitHub. I have already rotated these keys myself: [list the key names, not the values].
1. Make sure no secret key (Stripe secret key, Supabase secret or service_role key, OpenAI or other AI keys, email keys, database passwords) is in any file in the repository or in code that runs in the browser.
2. Read those secrets only on the server: from Edge Function secrets (Deno.env.get) or the host's environment variables.
3. Keep only values that are meant to be public in .env, such as VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY. [If this is not a Lovable project: add .env to .gitignore and create .env.example with the variable names and no values.]
4. Don't change how the app works, and don't change any other file.
Tell me which secrets I need to add in the Secrets or environment variables page, by name only.How to confirm it's fixed
- The old keys no longer work: the provider's dashboard shows them as deleted or expired.
- The app works with the new keys: sign-up, log-in and the payment page all open and work.
- On GitHub, the latest version of your repository has no
.envwith real secrets (in Lovable,.envhas onlyVITE_values). - Your live site doesn't serve the file: opening
https://your-site.com/.envin a browser shows a "not found" page, not your keys.
How to find out sooner next time
A key can leak in two other ways that GitHub won't warn you about: your live site can serve the .env file itself, or your AI tool can put a secret key into the code every visitor downloads. Heedza's security checks look at your live site from outside. They try to open /.env, /.env.local, /.git/HEAD and /.git/config, and read your home page and its code files for secret keys. If they find something, you get the finding and a fix prompt. Heedza doesn't read your GitHub repository, so it can't see a file that is only in your git history.
More on keys: OpenAI API key exposed in your frontend, Supabase service_role key exposed in the frontend, and the security checklist for apps made with AI tools.
Let Heedza check this for you. Heedza’s security checks looks for secret keys left in your page code and private files anyone can open. It checks again every time it checks your pages. It never logs in or fills in forms, so it can’t see errors that only show after that.
Made with Lovable, Bolt, Replit, Cursor or v0? Free for 1 site: start free or see prices.