How to check your APK for leaked API keys before you publish
Updated
Anything inside your APK is readable by anyone who has a copy. An APK is a
zip file. Free, widely used tools turn it back into readable resources and
something close to your source code in a minute: apktool for resources and the
manifest, jadx for code. Shrinking and obfuscation rename your classes and
methods, but the text of a string, including a key, stays exactly as you wrote
it.
So the useful question is not "is there a key in my app?" (there almost always is) but "is it one that is safe to be public?"
Which keys are safe to ship?
Some keys are designed to sit in client apps, because something else protects the data behind them:
- Firebase API keys. Firebase's own documentation says keys "restricted to Firebase services" are "OK to include in code or checked-in config files". Your data is protected by Security Rules and App Check, not by the key. See what actually protects your Firebase data.
- Supabase publishable (or legacy anon) keys. Supabase calls these "safe to expose" in a mobile app, on one condition: Row Level Security is enabled on every table. See whether your Supabase key is safe.
- Payment providers' publishable keys, the ones meant for the client, as opposed to their secret keys.
"Safe to ship" still means restricted. Firebase recommends a separate, restricted key for any Google Cloud API that is not a Firebase service, rather than reusing the one in your app.
Which keys must never be in an APK?
Anything that grants access by itself, with nothing behind it checking who is asking:
- Supabase secret or service_role keys. Supabase says: "Never put one in a browser, a shipped application, or source control." It refuses a secret key from a browser, but an Android app is not a browser, so from your APK it works, and used on its own it bypasses Row Level Security.
- AI provider keys. Firebase says the key for the Gemini Developer API "should never be included in your code or configuration files." The same is true of any AI API key: whoever holds it runs requests on your bill.
- Payment secret keys, cloud access keys and database passwords. These act as you, not as your user.
Where do keys hide in a build?
Rarely where you think you put them. Look in:
res/values/strings.xmland other resources, where configuration often ends up.BuildConfigfields and.envvalues. Build-time variables are compiled into the app as constants. Putting a secret in.envkeeps it out of git, not out of the APK.assets/, including bundled JSON config files.- The JavaScript bundle in React Native and Expo apps
(
assets/index.android.bundle). Hermes compiles it to bytecode, but string literals are still in it. libapp.soin Flutter apps, where Dart code is compiled ahead of time, string constants included.
How can I check my own APK?
Decode it and search it. With apktool and standard command-line tools:
apktool d app-release.apk -o decoded
grep -rE "sk-|sk_live_|sb_secret_|AKIA" decoded
Those patterns catch some well-known key formats: sk- for many AI providers,
sk_live_ for Stripe live secret keys, sb_secret_ for Supabase secret keys and
AKIA for AWS access key IDs. They do not catch everything, and binary files
such as Flutter's libapp.so or a Hermes bundle need strings run over them
first:
strings decoded/lib/arm64-v8a/libapp.so | grep -E "sk-|sb_secret_|AKIA"
Legacy Supabase keys are JWTs: long strings starting eyJ, with the role
encoded inside. Paste any you find into a JWT decoder; if the payload says
"role": "service_role", it is the secret one.
What should I do if I find one?
- Rotate it first. Every copy of the build already out there, installed on phones or saved by anyone who downloaded it, still has the old key. Removing it from the next build does not take it back.
- Move the call behind your server. Your app calls your backend (or a serverless function); your backend holds the secret and calls the provider. The app never sees it.
- Restrict what stays in the app. For keys that must be in the client, limit them to your app and to the services they are for, and protect the data behind them with the provider's access rules.
Leaked keys are one item on the pre-launch checklist. The rest is what Google checks: target API level, permissions and the Data safety form.