AI-coded apps

    Windsurf AI leaked my Google Maps API key. What do I do now?

    A Windsurf editor window beside a Google Cloud console showing a Google Maps API key under Credentials, with Android and iOS application restrictions being added, illustrating how to lock down an exposed Maps key in a vibe-coded mobile project

    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:

    1. Set the application restriction to Android apps.
    2. 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.
    3. Use the release certificate fingerprint for production. Debug builds need their own key.

    For iOS:

    1. Set the application restriction to iOS apps.
    2. 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:

    1. Create a new key with the correct application and API restrictions.
    2. Ship a release that uses the new key.
    3. Watch the old key's traffic in Cloud Console Metrics Explorer until it falls to noise.
    4. 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.

    ConcernRestricted key in the binaryServer-proxied Maps call
    Setup effortMinutes in Google Cloud consoleHours of backend work plus auth
    Protection against abuseYes, if restrictions are correctYes, plus your own rate limiting
    Works for native Maps SDK renderingYesNo, the SDK needs a key
    Works for Geocoding, Directions, PlacesYesYes, and recommended for cost control
    Cost predictabilityTied to user growthFully under your control
    Failure modeMisconfigured restriction equals open keyServer 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 .env is private on mobile. Variables prefixed with EXPO_PUBLIC_, REACT_APP_, or anything imported into a screen file is inlined into the bundle. The Expo team documents this directly for the EXPO_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.
    • #windsurf
    • #google-maps-api
    • #api-key-leak
    • #vibe-coding
    • #mobile-secrets
    • #android
    • #ios
    • #ai-coded apps

    Frequently asked questions

    Why does Windsurf inline a Google Maps API key into my mobile bundle?
    Because the upstream README examples and the Next.js or Vite training corpus both treat API keys as runtime values that live in code. Windsurf reproduces that pattern for React Native, Expo, Flutter, and native projects, where the same key gets inlined into the IPA or AAB at build time. The agent is not wrong about the syntax, it is missing the mobile-specific consequence, which is that the value becomes recoverable with unzip plus a text editor.
    How do I restrict my Google Maps API key for an Android app?
    Open the Google Cloud console, find the key under APIs and Services then Credentials, and set Application restrictions to Android apps. Add an entry per app with the package name and the 20-byte SHA-1 fingerprint of the signing certificate, using the release certificate for production and a separate key for debug builds. Then add an API restriction listing only the SDKs your app actually uses, like Maps SDK for Android.
    Do I have to rotate the key if it leaked, or is restricting it enough?
    Restricting is usually enough. Google's official guidance is that if a key has not been actively abused, you can migrate to new keys at your own pace. Rotation matters when Cloud Console Metrics Explorer shows real abuse: requests from unknown packages, sudden quota spikes, or charges you cannot trace. Mobile apps cannot be force-updated, so deleting the old key too early breaks users on the previous version.
    Can I see if someone is already abusing my exposed Google Maps key?
    Yes. Open Cloud Console Metrics Explorer, filter by the API key, and look at request volume per API, per country, and per HTTP referrer or app. A spike that does not match your real user base, requests originating from unexpected packages, or sudden growth in Geocoding or Places billing is the signal. Setting a daily budget alert in Cloud Billing gives you a cheaper second line of defense before a large charge lands.
    Is there a way to keep the Google Maps key off the binary entirely?
    Partially. The native Maps SDK for Android and Maps SDK for iOS authenticate locally, so the key still has to ride in the app. For everything else, including Geocoding, Distance Matrix, and Places Details, the right pattern is a server proxy: the mobile app calls an authenticated endpoint on your server, and the server adds the API key. That keeps the cost-sensitive endpoints under your control.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free