Security basics
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.
In short
- A Google API key exposed in your frontend is usually fine, because a Google key is a billing identifier and not a password.
- What makes it fine is an HTTP referrer restriction: a setting that tells Google the key only works from your own website.
- It is free, it takes about five minutes, and it is the one thing worth doing today.
- A Google service account file in your app is the different and urgent case. That one is a real credential.
Open your app in a browser, view the page source, and search for AIza. If a
forty-character string comes back, someone has probably already told you it is
"exposed", and probably did not say what to do next.
Here is the part that most advice on this gets wrong: a Google API key in your frontend is not the same kind of problem as a Stripe secret key in your frontend, and the fix is not to hide it. Treating the two the same way means either panicking about your map or, worse, learning to shrug at the word "exposed" before the day it matters.
Between 12 and 14 August 2026 we ran the same nine external checks over 30,998 live apps built with Lovable, Bolt, v0, Replit and Base44. A Google API key turned up in 1,142 of them. A Stripe secret key turned up in three. The full numbers are in our scan report.
Is it bad that my Google API key is exposed in the frontend?
Usually not. It depends entirely on whether the key is restricted.
A Google API key is not a password. It is closer to an account number: it tells Google which project to bill and which quota to draw down. On its own it says nothing about who is making the request.
That is why it can live in your page. The map is drawn by your visitor's browser, so the browser is what has to send the account number to Google. There is no version of this where the map loads and the number stays hidden.
The protection comes from a second setting, held on Google's side, that says which websites the number may be used from. Google calls it an HTTP referrer restriction. With it set, the key printed in your page works on your site and returns an error everywhere else.
Why your app ships the key at all
Because the request goes out from your visitor's browser, not from your server.
When someone loads a page with a map on it, that browser talks to
maps.googleapis.com directly. It has to say which Google project the request
belongs to, and that identifier has to be in the page, because the page is where
the request is made from.
Google knows this. It is why the restriction exists as a separate control: the identifier was always going to be public, so the thing that decides who may use it lives somewhere the public cannot reach.
How to restrict a Google API key
It is free, it is about five minutes, and you do not need to touch your app's code. In the Google Cloud console:
- Go to APIs & Services → Credentials and click the key you found.
- Under Application restrictions, choose Websites.
- Add your domain. For a site at
yourapp.com, addhttps://yourapp.com/*. Add your preview or staging domain too, if you have one, and remember the.lovable.appor.vercel.appaddress your app was published on before you attached a custom domain. - Under API restrictions, choose Restrict key and tick only the APIs your app actually calls. A map with places search needs the Maps JavaScript API and the Places API, and nothing else.
- Save, wait a few minutes, then load your own site and check the map still draws.
If step 3 is wrong, you find out immediately and visibly: the map fails with
RefererNotAllowedMapError in the browser console, which names the exact
problem. Add the missing domain and it works again. This is a reversible change,
which is why it is worth doing before you have finished reading about it.
One thing not to do: Google's own troubleshooting guidance says IP address restrictions are for server-side keys and are not suitable for a key used by a browser. If you restrict a frontend key by IP, your app breaks for every visitor.
If you would rather see which keys your app is actually shipping before you go key by key, our free scan reads your live site from outside and tells you what it can find. It takes about 20 seconds and needs no account: scan your app.
Which Google credentials belong in a browser
| Credential | What it looks like | In your frontend? | What actually protects it |
|---|---|---|---|
| Maps or Places browser key | AIza… | Belongs here | The referrer restriction, plus a quota cap |
| Firebase web config key | AIza… | Belongs here | Firebase Security Rules and App Check |
| A key you use from your server | AIza… | Never | Nothing, once it is public. Keep it server-side |
| Service account | a JSON file containing "private_key" | Never | Nothing. This one is a real credential |
The first three rows are the same string. A Google API key does not say on its front what it is for, so the question is never "what does this key look like" but "what is this key allowed to do", and that answer lives in the console rather than in your code.
The Firebase key is meant to be public
This is the row people ask about most, and Firebase's own documentation answers it plainly: API keys for Firebase services are safe to include in code or configuration files, because they do not control access to anything. Firebase Security Rules and App Check decide that.
If that sounds familiar, it is the same arrangement Supabase uses. The publishable key identifies the project, and the rules behind it decide row by row who may read what. In both cases the key is safe only because of those rules, so the rules are the thing to go and look at: switching them on is not the same as being protected.
What actually happens when an unrestricted key leaks
A bill, or an outage, or both. Not a data breach.
Automated scrapers crawl public pages collecting AIza strings, and an
unrestricted key works from anywhere, so yours ends up serving map tiles and
place lookups for somebody else's project. Google names this directly in its own
getting-started documentation: the reason to set restrictions is to prevent quota
theft.
You usually notice it in one of two ways. The invoice is larger than last month for no reason you can point to, or your app's maps stop drawing partway through the month because the project has run through its quota and every visitor now sees a grey box.
Both are recoverable. Neither exposes your users' data, because a Maps key cannot read your database. That is the honest difference between this finding and a leaked database key, and it is why our scanner reports a Google API key as something to check rather than as an emergency.
The one Google credential that does behave like an emergency is a service
account. If your app is shipping a JSON file with "type": "service_account" and
a "private_key" block in it, that is a real credential with real permissions on
your cloud project. Delete the key in the console, then look at what it could
reach.
What to do right now
What to do
- Search your live site's page source for
AIza. Every match is a key to account for. - Restrict each one in the Google Cloud console: Websites under Application restrictions, your domain, and only the APIs you use. Free, about five minutes, no code change.
- Set a daily quota cap on the key while you are in there. A restriction reduces the traffic; a cap limits what any of it can cost.
- Leave your Firebase config key where it is. Check instead that your Security Rules are doing the work, and keep the Gemini API off that key's allowlist.
- If you find a
service_accountJSON file in your frontend, treat it as urgent. Delete the key in the console rather than only removing the file, because your version history still has the old value.
If you would rather work through this as a list, the 10-minute security checklist covers this alongside the other things worth turning off in a newly launched app. And for the wider question of which keys belong in a browser at all, we have a guide to telling publishable keys from secret ones.
FAQ
Someone found my Google Maps key in the page source. Should I rotate it?
Restrict it first, which takes about five minutes and does not break anything. Open the key in the Google Cloud console, set Application restrictions to Websites and add your domain, then set API restrictions to only the APIs your app uses. Rotating comes after, and only if the key was unrestricted for a while and you can see usage you did not cause.
Can I just hide the key so nobody finds it?
No. Your visitor's browser has to send the key to Google to load the map, so anything the browser can use, a visitor can read. Minifying or renaming only slows someone down for a few seconds. Restricting the key is the fix that actually works, and it works whether or not anyone finds the key.
My key is restricted to my domain. Can someone still use it?
A referrer header can be forged, so a determined person can send requests that claim to come from your site. What the restriction removes is the easy case: an automated scraper that finds keys on public pages and uses them straight from its own server. That is the traffic that produces surprise bills, so the restriction is worth setting even though it is not a wall. Set a daily quota cap on the key as well, and you have a ceiling on what any of it can cost you.
Firebase put an API key in my config file. Is that a mistake?
No, and Firebase documents it. A Firebase web API key identifies your project rather than authorising access, and access is decided by Firebase Security Rules and App Check instead. It is meant to be in your code. The one thing to avoid is adding the Gemini Developer API to that key's allowlist, because that turns a key you publish on purpose into one that can spend money.
What if I find a file with "type": "service_account" in my app?
That is a different situation and it is urgent. A service account key is a real credential with a private key inside it, and it belongs on a server. Delete the key in the Google Cloud console under IAM and Admin, then Service Accounts, take the file out of your frontend, and check your billing and audit logs. Deleting it from your code alone does not close the door, because the old value still exists in your version history.