If Windsurf AI just helped you ship a mobile build and a friend, a scanner, or a Google Cloud quota alert told you that your Google Maps API key is now public, this article is for you. The situation feels worse than it usually is, and the right fix takes about an hour if you work in the correct order.
Short answer
The short answer is that Windsurf wrote your Google Maps API key into a file that gets bundled into the IPA or AAB, where anyone with the artifact can read it. In most cases the bill grows because the key has no application restriction, not because the string was visible. Open the Google Cloud console, attach an Android or iOS application restriction plus an API restriction to the existing key, then rotate it only if monitoring shows real abuse. Google's Maps Platform security guidance is the canonical source for this sequence.
What you should know
- A Maps API key in a mobile app is not a secret. Any package that ships an iOS or Android map carries the key somewhere in the binary; the protection is the restriction, not the secrecy.
- Unrestricted keys are the actual risk. Google charges the project for every call made with the key, so an unrestricted leak can move quickly to a billing event.
- Restrictions are platform-specific. A single key cannot carry both an Android package restriction and an iOS bundle restriction at the same time, which is why separate keys are required for separate clients.
- Rotation does not retire shipped users. A new key only protects users on a new version; the old key keeps working until you delete it.
- Recent Google API behavior matters. Older Maps keys can now reach Gemini endpoints if Gemini is enabled on the same Cloud project, so an unrestricted leak is no longer Maps-only.
How did the Google Maps API key end up in my Windsurf-generated app?
The short answer is training-data inertia plus a one-file convenience pattern. When Windsurf is asked to add a map to a React Native, Expo, Flutter, or native Android or iOS project, it almost always reaches for the first example in the upstream library README, which hard-codes the key inside AndroidManifest.xml, AppDelegate.swift, or a JavaScript config file. The generated code compiles, the map renders, and the agent moves on.
There is a quieter second mode. Windsurf will often write the key into a .env file or a Constants module and then refer to it via process.env.GOOGLE_MAPS_API_KEY. On a web project this leaves the value on the server. On a mobile project, the build step inlines the literal into the bundle. The diff looks clean, the artifact is not.
The OWASP Mobile Application Security Testing Guide lists hardcoded keys in application binaries (MASWE-0005) as a recurring weakness for both iOS and Android. The position is that any string sitting inside a shipped binary is recoverable with unzip plus a text editor, no reverse engineering required.
What can someone actually do with a leaked Google Maps API key?
The short answer is run up your bill. A stranger with your unrestricted key can call any Maps Platform API the key is allowed to reach (Geocoding, Distance Matrix, Places Details, Directions) and your Google Cloud project pays for the requests. Most reports of large bills follow this exact pattern: the attacker scrapes data, the developer gets the invoice.
Beyond billing, there are two specific risks worth naming. The first is the Gemini exposure documented by Truffle Security, which found that older Maps API keys can quietly reach the Gemini API once that service is enabled on the same Cloud project. The second is quota burn: an attacker can intentionally exhaust your daily quota and make the map stop loading for real users.
A restricted key avoids most of this. With an Android or iOS application restriction in place, Google rejects calls that do not present the matching package or bundle identifier. The key is still inside the binary, only requests originating from your signed app accept it.
How do I restrict the key to my Android package or iOS bundle?
The short answer is to open the Google Cloud console, find the key under APIs and Services then Credentials, and add both an application restriction and an API restriction. The Google Cloud API Keys documentation describes the exact fields.
For Android:
- Set the application restriction to Android apps.
- Add an entry per app with the package name (for example,
com.acme.maps) and the 20-byte SHA-1 fingerprint of the signing certificate. - Use the release certificate fingerprint for production. Debug builds need their own key.
For iOS:
- Set the application restriction to iOS apps.
- Add the bundle identifier of each shipped iOS app.
For both platforms, add an API restriction that lists only the SDKs and APIs the app actually calls (Maps SDK for Android, Maps SDK for iOS, Places SDK, Geocoding API). The Google Maps Platform best practices blog recommends one key per source, which means at minimum: one for Android, one for iOS, one for any web client, and one for any server-side call.
When should I rotate the key, and how do I do it without breaking shipped users?
The short answer is rotate when the Cloud console shows real abuse, not the moment the key appears in a public repo. Google's Maps Platform security guidance makes the point directly: if a key has not been actively abused, you can move your apps to new keys at your own pace.
The mobile detail behind that advice is that a shipped app cannot be force-updated. Users on an older AAB or IPA keep calling with the old key until they install the new version. That can take weeks. The supported pattern is:
- Create a new key with the correct application and API restrictions.
- Ship a release that uses the new key.
- Watch the old key's traffic in Cloud Console Metrics Explorer until it falls to noise.
- Delete the old key only when the remaining traffic is acceptable to break.
If abuse is already happening on the old key, the calculation flips: delete the old key now and accept that older app versions will see map errors until users update.
How does an in-binary restricted key compare to a fully proxied call?
A restricted key is cheap to set up and good enough for most map use cases. A server proxy is the safer pattern for anything price-sensitive or quota-sensitive.
| Concern | Restricted key in the binary | Server-proxied Maps call |
|---|---|---|
| Setup effort | Minutes in Google Cloud console | Hours of backend work plus auth |
| Protection against abuse | Yes, if restrictions are correct | Yes, plus your own rate limiting |
| Works for native Maps SDK rendering | Yes | No, the SDK needs a key |
| Works for Geocoding, Directions, Places | Yes | Yes, and recommended for cost control |
| Cost predictability | Tied to user growth | Fully under your control |
| Failure mode | Misconfigured restriction equals open key | Server downtime equals no maps |
A common pattern for cost-sensitive apps: keep a restricted key inside the binary for the native map view (which the SDK has to authenticate locally) and use a server proxy for everything else, like Geocoding or Distance Matrix. The mobile app sends an authenticated request to your server, the server adds a different API key, and the response comes back without the key ever touching the client.
For builders who want an outside read of their compiled AAB or IPA before submission, PTKD.com (https://ptkd.com) is one of the platforms focused on pre-submission scanning aligned with OWASP MASVS for AI-generated and no-code mobile apps. A scan flags exactly this pattern: a Maps key inlined into the bundle without a matching restriction in the project's Cloud console.
What to watch out for
A few specific failure modes show up repeatedly in vibe-coded projects:
- Sharing a single key across Android, iOS, and a web app. The first restriction you add invalidates the others, because a key can only carry one application-restriction type at a time. Use separate keys per source.
- Forgetting that the debug build has a different SHA-1. A key restricted to the release certificate will not work for a local debug build, and a key restricted to the debug certificate will fail for store users. Maintain a debug key and a release key.
- Assuming
.envis private on mobile. Variables prefixed withEXPO_PUBLIC_,REACT_APP_, or anything imported into a screen file is inlined into the bundle. The Expo team documents this directly for theEXPO_PUBLIC_prefix. - Ignoring Gemini exposure on legacy keys. Per the Bleeping Computer report, keys created before Gemini was enabled on a project can now reach Gemini endpoints. Either restrict them tightly or rotate before that exposure matters.
Key takeaways
- What drives billing damage is the absence of application restrictions, not whether the key string is in a public repo. Restrict every Maps key to a package, bundle, referrer, or IP before worrying about secrecy.
- Use separate keys per platform. One Android key, one iOS key, one web key, one server key. Each carries one application restriction plus the smallest API restriction that still works.
- Rotate when monitoring shows abuse, not when the key shows up in a public repo. Old app versions keep calling with the old key, so deleting it too early breaks shipped users.
- Keep cost-sensitive Maps Platform calls (Geocoding, Distance Matrix, Places Details) behind a server proxy. Native map rendering is the one path where the key has to ride in the binary.
- An external scan of the compiled AAB or IPA catches inlined Maps keys without a matching Cloud console restriction. Platforms like PTKD.com (https://ptkd.com) are built for that pre-submission read, aligned with OWASP MASVS data storage categories.



