All writing
10 min read

API keys in mobile apps: why they leak and the relay fix

Any API key shipped in a mobile or front-end app can be recovered. Keep it on a relay proxy, then give that relay auth, rate limits and a hard spend cap.

Asghar Ali · Chief Technology Officer, Kakushin.io LTD

An API key shipped in a mobile or front-end app is public, however well it is hidden. OWASP's entry for this weakness says "anyone who downloads the package can recover it" (MASWE-0004, read on 6 November 2026), and OpenAI is blunt: "Never deploy your key in client-side environments like browsers or mobile apps" (OpenAI key safety guide, read on 6 November 2026). The fix is to keep the real key on a server you control and have the app call that server: a relay proxy. The relay then needs its own authentication, rate limits and spend caps, or it gives away the same account through a different door.

This is for the team shipping an iOS, Android or single-page app that calls a paid or sensitive API, and for whoever commissions that work. It covers where keys leak, which keys are meant to be on a device, what a relay changes and leaves open, and how to move an app already in the stores without breaking old versions. For the browser version with a worked demo, see secrets on the wrong side.

On the customer apps of a European national electricity utility, I introduced a relay-proxy architecture that removed every client API key from the shipped binaries. That is the experience behind this essay. The tables below are general guidance, not a description of that project.

Why can't you hide an API key in a mobile app?

Because the person you are hiding it from holds the device. The app has to use the key, so the key, or everything needed to rebuild it, sits on hardware the reader controls. Three common answers do not change that:

  • Obfuscation. OWASP files obfuscation, white-box cryptography and runtime protection under "Harden Only as a Last Resort", because they "deter but do not prevent extraction" (MASWE-0004). The MASVS resilience group adds that such techniques "must not be used as a substitute for proper security architecture" (MASVS-RESILIENCE, read on 6 November 2026).
  • The Keychain or Keystore. The right home for a per-user token your server issued. A shared vendor key still has to reach the device first, and then leaves it in a request.
  • Certificate pinning. I used pinning on those utility apps as well. It helps against someone on the network impersonating your server. It does not hide anything from the person holding the phone, and OWASP's testing guide treats intercepting app traffic as a critical part of security testing.

Leaked keys also tend to keep working. GitGuardian, which sells secret scanning, counted 28.65 million new hardcoded secrets in public GitHub commits in 2025. AI-service secrets reached 1,275,105, up 81% on the year, and more than 64% of the valid secrets it found from 2022 were still valid when retested in January 2026 (State of Secrets Sprawl 2026, read on 6 November 2026). Those figures describe repositories, not app packages, but the last one is the one to remember: nobody is coming to revoke your key for you.

Where do API keys leak from a mobile or front-end app?

The OWASP Mobile Top 10 2024, the latest list as of 6 November 2026, opens with M1: Improper Credential Usage and calls hardcoded credentials in source or config files "a clear indicator of vulnerability". The first four rows below are the routes MASWE-0004 names; the rest are where I would look next.

WhereHow the key gets thereWhat to check this week
App source codeCompiled in as a constantSearch your release build for the prefix of every key you hold
Assets and configProperty lists, manifests, string resources, bundled filesUnpack your own APK or IPA and scan the contents
LibrariesAn SDK's config or setup carrying a keyList what each SDK asks you to embed, and who pays for its use
Build leftoversStaging URLs, source files, map filesStrip them from release packages
Front-end bundleVITE_ and NEXT_PUBLIC_ values inlined at build timeScan the built output, not only the source
Network trafficThe key travels in a request the device owner can readOn your own test device, list every third-party host the app calls
LogsDebug logging or crash reports that capture headersSearch your logs for Authorization and for key prefixes
Repository historyA commit made before anyone noticedRun a scanner over the full history once, then on every push

Which keys are meant to be on the device?

Not every key is a secret. Ask one question: if a stranger copied this value, what could they do, and who pays?

KeyOn the device?Why (provider docs, read on 6 November 2026)
Stripe publishable keyYesStripe says it can go in front-end code or apps you distribute, and that only publishable keys are safe to expose
Firebase key restricted to Firebase servicesYes, with conditionsFirebase says it need not be treated as a secret, because Security Rules and App Check do the protecting. Never allow the Gemini Developer API on a public key
Google Maps client SDK keyYes, restrictedRestrict it by app and by API. Google warns you are "financially responsible" for abuse of unrestricted keys
Short-lived token your server mintsYesGemini's ephemeral tokens can be extracted too, but they expire quickly and can be restricted. Live API only at this time
AI provider keys, Stripe secret and restricted keys, SMS, email, database or admin credentialsNoThey spend money or reach data. Google's Gemini docs say keys compiled into client-side code can be extracted by users

What does a relay proxy change?

A relay is a small service you run between the app and the third party. The app sends a narrow request with its own credential. The relay checks it, adds the real key, calls the third party and returns the answer. MASWE-0004 lists this first among its mitigations, "keeping the static secret server-side".

Diagram comparing two designs. Before: the app on a phone holds the vendor key and calls the third-party API directly, so anyone who downloads the package can recover the key, and rotating it needs a new app release. After: the app holds only a short-lived user token and calls your relay. The relay authenticates each caller, allows fixed operations only, sets model and size limits, rate limits per user and per IP, keeps a spend ledger with a hard cap, checks objects per user, treats attestation as an extra signal and logs no keys. Only the relay sends the vendor key to the third-party API.
The relay moves the key off the device. The abuse question moves with it, so the relay becomes the thing to defend.

Three things improve:

  • The key leaves the package. Future builds have nothing to recover.
  • Rotation stops needing a release. Google notes that in Android and iOS apps, keys "aren't replaced until customers update their apps" (Maps security guidance). Behind a relay you change one server secret.
  • You can tell callers apart. The third party sees one key. The relay sees which user asked, so you can limit, log or cut off one person.

What does a relay proxy not fix?

An unauthenticated relay is the same key with an extra hop. Whoever finds the URL spends your money, and the URL is as easy to find as the key was. Google says both in the same guidance: "Always require authentication for client access to the proxy servers" and "Don't allow clients to relay arbitrary API calls using the proxy."

This is the table I would review a relay against:

ControlWhy it mattersWhat a pass looks like
Caller authenticationA shared secret in the app is just another shipped keyEvery request carries a per-user token that expires (MASWE-0004 suggests about an hour). No token or an expired one gets a 401
Fixed operationsAn open relay hands out your vendor accountOne route per feature. The server builds the vendor request, and the client cannot choose the host, path or model
Request limitsSize is costThe server caps input size and output length
Rate limitsScripts do not get tiredLimits per user and per IP; over the limit gets a 429
Spend capAlerts do not stop a billA hard limit at the provider, plus your own per-user ledger
Object-level checksThe relay acts for one user, on that user's dataA two-account test passes on every route
AttestationIt filters some scripts, not determined peopleVerified on the server, and used alongside other signals
LoggingYou cannot stop what you cannot seeWho, which operation, what it cost. No keys, tokens or auth headers
UpkeepIt is now a server you ownKey in a secret store, one per environment, rotation rehearsed, runtime patched

Two rows need a source. On spend, OpenAI says "Spend alerts alone don't stop API traffic" and points to hard enforcement, noting that spend may slightly exceed the limit (read on 6 November 2026). OWASP's API4:2023 Unrestricted Resource Consumption asks for spending limits on all service providers. On a reference app I built AI spend controls with a per-IP ledger and abuse heuristics, and I put that ceiling first on any AI feature.

On attestation, Firebase App Check says it prevents some abuse but not all, and Google says Play Integrity should not be your sole anti-abuse mechanism (both read on 6 November 2026). A valid attestation is one more signal, not an identity.

How do you move a live app off an embedded key?

Order matters, because old versions keep calling with the old key until people update. This example is illustrative, not a client case: a made-up recipe app, version 3.2, has an AI vendor's key in a config file and calls the vendor directly.

  1. Assume the key is copied, and limit it today. Narrow its permissions and set a hard spend limit. If you can already see abuse, revoke it now and accept that the feature breaks in 3.2.
  2. Build the relay with a new key. Never reuse the exposed one. Work through the table above before any client calls it.
  3. Ship 3.3 with no key. Unpack the release build and confirm that neither the old key nor its prefix appears.
  4. Watch the old key. Its traffic should fall as people update. Traffic that holds steady, or comes from places your users are not, is probably someone else.
  5. Set a cut-off. Add a minimum-version check, warn people on 3.2 and pick a date.
  6. Revoke the old key, then stop it coming back. Deleting it from the code does not un-publish it, because old packages stay in circulation. Add a secret scan to the release pipeline.

How do you check your own build?

Only on apps you own. The MASWE-0004 page lists no MASTG tests for this weakness as of 6 November 2026, so write your own release check:

  1. Unpack your release APK or IPA; both are ZIP archives.
  2. Run a scanner over the files, such as gitleaks with its dir mode, and search them for the prefix of every key you hold.
  3. On a test device you control, capture the app's traffic and list each third-party host. A paid API called directly from the app is the red flag.
  4. Fail the build if anything turns up, on every release.

What this cannot tell you

  • A clean scan is not proof. Scanners find known patterns, and a split or encoded key may not match. The better question is architectural: does this app need a vendor secret at all?
  • I have no count of leaking apps. The GitGuardian figures cover public GitHub commits, and I did not find a primary source I would trust for app packages, so I quote none.
  • Provider rules change. Everything cited here was read on 6 November 2026. Check your own provider's current docs.
  • A relay review is not a penetration test. It says nothing about the rest of your API. The wider order of work is in application security best practices.

Frequently asked questions

Can I store an API key in the iOS Keychain or Android Keystore?

Store tokens your server issued to that user there. A shared vendor key still has to reach the device before it can be stored, and it leaves again in a request, so the keystore does not make it private. MASWE-0004 lists keystores as a separate mitigation, after keeping static secrets on the server.

Is restricting a key to my app's bundle ID enough?

It narrows what a copied key can do, and Google calls restriction best practice for Maps keys. It is not enough for a key that spends real money or reaches data. MASWE-0004 treats restriction as the fallback for secrets that must be hardcoded, to reduce the damage when they are exposed.

What about an app with no login?

That is the hard case, because there is no user to authenticate. Issue a short-lived token per install, add attestation, keep the limits low and put a hard cap on total spend, so your worst day has a known cost. Move expensive features behind a sign-in.

Should one relay key serve all my users?

For a third party with no idea of your users, usually yes, and the relay does the separating through per-user limits and logs. Where the service supports each user's own token, use that. It matters most when an AI agent calls tools, as I cover in AI agent tool permissions.

If you want this checked

I review mobile, web and AI apps for this: the release build and its traffic, the relay against the table above, two-account authorisation tests and a written fix list. The scope is on my hire page.