Are my API keys exposed in my GitHub repository or in my published site?

The answer

Two different places, two different answers. A key in your repository is exposed if the repository is public, and stays exposed in the history after you delete the file. A key in your published site is exposed if the build inlined it, which happens by design to anything named for client use. Neither produces an error.

By Muhammad Bilal9 min read

The short version

  • A scan of nearly two thousand public repositories from AI-built apps in August 2026 found a committed environment file in roughly a quarter of them, and only about one in forty was clean of any critical or high finding.
  • The same body of work contains a reassuring counter-finding: a separate scan looking specifically for the database admin key in client code found none at all. Which key you are looking at matters enormously.
  • Some keys are meant to be public and seeing one in your browser is not a problem. What protects you in those cases is the permission layer behind the key, not the key's secrecy.
  • The build step is the path most people have never heard of. Anything named for client use is compiled into your published JavaScript, which means a private repository gives you no protection against it at all.
  • Deleting a committed file does not remove it from history, and rotating comes before fixing. Automated scanning of public repositories for live credentials is an industry, not a hypothetical.

"Are my keys exposed" sounds like one question. It is two, they have different answers, and the checks are completely different. People generally find one of them, fix it, and remain exposed through the other.

Question one: is a key sitting in my source code repository?

Question two: is a key compiled into the site my visitors download?

You can have either without the other. A private repository does nothing about the second. Cleaning up the second does nothing about the first. Here is how each one happens and how to check.

What the measurement actually says

Until recently the honest answer to "how common is this really" was a shrug. In August 2026 somebody did the work.

A researcher scanned 1,969 public repositories belonging to apps built with Lovable — findable because the tool writes a build plugin into every project, which leaves a signature you can search for. The headline numbers:

Roughly 23 per cent had a committed environment file. Not a key pasted into code by accident, but the file whose entire purpose is holding secrets, checked into version control and readable by anyone.

Around 38 per cent carried at least one critical or high finding.

About 2.6 per cent were clean of any finding at all.

The author of that work was careful in a way that makes the numbers more trustworthy rather than less, and I want to repeat the caveat rather than quietly drop it: a broader hardcoded-credential rule tripped on about 42 per cent of the repositories, and when they manually reviewed a sample of those, most turned out to be false positives. They flagged that figure as a ceiling rather than a finding. The committed-environment-file number is the solid one, because a file either is or is not in the repository.

One incidental detail from the same scan that is worth carrying away for other reasons: the median repository was about four megabytes and the largest tenth were over eighty-six. Whatever your plan for reviewing your own code is, "paste it into a chat window" is not it.

The counter-finding, which should calm you down

There is a widespread fear that AI tools leave the database admin key — the one that bypasses every permission rule you have — sitting in client-side code.

Somebody checked. A separate scan of forty-seven public repositories from the same platform, looking specifically for that key in client code, found none. Zero for forty-seven.

That is a small sample and it does not prove the case is impossible. But it does mean the thing most people are frightened of is not the thing that is actually happening, and the effort is better spent on what the larger scan found: ordinary environment files, committed, containing ordinary provider keys.

This matters practically, because it changes what you should look for.

Which keys are supposed to be public

Before checking anything, know which alarms are real.

Your database's public key is meant to be visible. It ships inside every app built on that platform, it appears in the browser of every visitor, and on its own it grants nothing — what it can reach is decided entirely by the permission rules on your tables. A public key on a properly configured database is a key to a locked door. If your permission rules are wrong, that is a different and more serious article, and it is the one about logged-in users seeing each other's data.

A payment provider's publishable key is meant to be visible for the same reason. It can create a payment attempt. It cannot read your customers or move money.

What is never meant to be visible is the database admin key, any payment provider secret key, and — this is the category that has changed most in the last two years — any provider key that costs money per call. A leaked model API key is now among the most expensive things you can lose, because the person who finds it can spend your money at industrial speed and the first sign is the invoice.

Why keys end up in the repository

Three mechanisms, all mundane.

The assistant writes the environment file before it writes the ignore file. By the time the ignore rule exists the file is already tracked, and adding the rule afterwards does not untrack it. This single ordering accident explains most of what that scan found.

The repository was created public. Some flows default to it, and "public" reads to a non-developer as "visible" rather than "readable, cloneable and continuously scanned by automated tooling".

The fix was applied in the wrong place. Somebody notices, moves the key into the hosting platform's environment settings, redeploys, and everything works. The application is now reading from the right place. The file is still committed, and the key is still in the history.

The build step, which almost nobody knows about

This is the part that is genuinely surprising, and it is the reason a private repository is not the end of the story.

Front-end build tools have a convention: environment variables with a particular prefix are intended for the browser, and the build writes their values directly into the JavaScript it produces. This is not a leak or a bug — it is the documented, intended behaviour, and it is how your app knows its own database address.

The consequence is what catches people. That value is now part of the published output. It is served to every visitor. There is nothing at runtime to intercept, because nothing is being transmitted — it was compiled in before the site was ever deployed.

So if an assistant, trying to make something work in the browser, moved a key that should have stayed on the server into a client-prefixed variable, that key is now in your published site, permanently, for everyone. Your repository being private has no bearing on it whatsoever.

How to check both, in about fifteen minutes

For the repository. In the project folder, run:

git log --all --oneline -- .env .env.local .env.production

Nothing printed means no environment file was ever committed, and you are clear on that count. A list of commits means those files are in your history, and deleting them later did not help.

Then check for keys pasted directly into code, which the above will miss. Search the whole history for the prefixes that matter to you — a payment provider's secret key prefix, your model provider's key prefix, the string that identifies an admin database key. Your host's search will do this across the repository; for history, a search through the log will.

For the published site. Open your live site, view the page source, and open the main JavaScript bundle it references. It will be unreadable, which is fine — you are searching, not reading. Use the browser's find and look for the first several characters of each key you know you have. Also search for the prefixes of the keys that should never be there.

The better version of the same check is to do it on the build output before you deploy: build locally, then search the output folder for the same strings. That turns a discovery into a gate.

What to do if you find one

Rotate first. Fix second. This order is not negotiable and the instinct is usually the wrong way round.

A key that has been public stays compromised after you patch the code that exposed it, because whoever copied it still has it. Patching first and rotating later leaves a window that is measured in however long you take, and against automated scanning that window is generous.

So: rotate the key at the provider. Then fix the mechanism — untrack the file, add the ignore rule, move the value into the hosting platform's environment settings, and if it was a build-inlined variable, move the logic that needs it onto the server where it belongs. Then redeploy and re-run both checks.

Assume compromise if the repository was ever public, even briefly, even if it is private now. That is not paranoia. Scanning is automated, continuous and fast, and the interval between a key appearing publicly and being collected is typically minutes.

The second line of defence, which people skip

Rotating a key deals with the key you found. It does nothing about the next one.

Every provider that charges by usage lets you cap spending, per key or per project, and almost nobody building this way has set one. It is five minutes per service and it converts the worst possible outcome — an unbounded bill from a key you did not know had escaped — into a bounded one that stops on its own.

Do this for every model provider, every paid API, and your hosting platform. It is the cheapest insurance in this entire article.

What the platforms have started doing

Worth knowing, and worth not over-relying on.

Since July 2026 Lovable automatically revokes its own workspace API keys if they are committed to a public repository. Your code host offers push protection that blocks commits containing recognised credential patterns, and it is worth switching on if it is not already.

Both are genuinely useful. Both have the same limit: they recognise the credentials they know about. The provider key for the small service you integrated last month, and the model API key in a format nobody has written a rule for, will pass straight through.

If you would rather have somebody check properly

The two checks above find the common cases and I would encourage you to run them today. What they do not find is a key in a format nobody thought to search for, or a service you forgot was integrated, or the third path — a key that is correctly stored but readable by more people than you intend because of who has access to the account.

I do a Production-Ready Audit that covers all of it: repository history searched properly rather than by prefix guessing, the built output examined for anything inlined, every provider key checked against where it is used and whether it is scoped and capped, and a rotation list in priority order. From $499, back in five to seven days.

You can also just send me a screenshot of your environment settings with the values blurred. The names alone tell me most of what I need, and I will tell you which ones are in the wrong place, at no charge and with nothing attached.

The rotation and repair work is described on the AI SaaS rescue page. If you have not yet run the four checks that find data readable with no login at all, do those first — they are quicker and they find a different problem, and they are in is my Lovable app secure. The harder case, where every login works and the app still hands the wrong person the right data, is in your login works, that does not mean your data is protected. And the broader question of who can legitimately read your code and your data, which is not about leaks at all, is in who can read your code and your data.

Follow-up questions

What people ask next

I can see a long key in my browser's network tab. Is that bad?

Probably not, and this is the single most common false alarm. The public database key and a payment provider's publishable key are both designed to be visible in every browser that loads your app, and they grant nothing on their own. What matters is what sits behind them. Decode the value or check its prefix before worrying: it is the admin key and the secret key that are emergencies.

My repository is private. Am I safe?

Safe from one of the two problems. A private repository means the committed file is not readable by strangers, which is genuinely worth having. It does nothing at all about keys that the build compiles into your published site, because that output is served to every visitor by design. Private repository, public bundle — the two are unrelated.

I deleted the file and pushed. Is it gone?

No. Version control keeps history, which is the entire point of it, so the old version with the key in it is still retrievable by anyone who can read the repository. Removing history is possible and fiddly, and it is also the wrong first move. Rotate the key. Once a key has been rotated, whether the old one is still visible in a commit from March stops mattering.

How would anyone even find my repository?

Automatically, within minutes. Scanning public repositories for live credentials is a fully industrialised activity on both sides — security researchers and otherwise. One person doing it routinely reported finding more than twenty live keys a day, including live payment provider secret keys. Nobody has to be interested in you specifically for this to reach you.

Related reading

Production-Ready Audit

Every table's row-level security reviewed, keys checked and rotated, auth and payments tested — back as a written fix list in priority order.

From $499 · 5–7 days

Muhammad Bilal, Full Stack AI Developer

Muhammad Bilal

Full Stack AI Developer · Faisalabad, Pakistan

I build and rescue production AI SaaS products with Next.js, Supabase, Stripe and Claude. Most of my work is finishing apps that were started with Lovable, Bolt, Cursor or Replit and stalled somewhere between working and shippable.

SF
MS
BK
AS

5.0★ · 100% job success · 35+ projects delivered

All articles · RSS