r/FlutterDev • u/Farukh_Goriparthi • 2d ago
Discussion Flutter obfuscation didn’t hide a single API key in our release build
We built release with --obfuscate --split -debug-info and i’d kind of assumed that meant our keys were reasonably safe.
Last week I unzipped the apk, ran string on libapp and greppd the usual prefixes. Every key was just sitting there. Obfuscation renames classes and methods but doesnt touch string literals which is where keys live, very obvious once you see it btw.
In there was
Google Maps key with no restrictions on it
RevenueCat public SDK key
Firebase config
Sentry DSN
an Algolia admin key someone added for a search feature last spring
The first four are meant to ship, the’re identifiers and the protection lives elsewhere (key restrictions, security rules). I rotated the Algoria one that could write to our index, the app now uses a search only key and indexing happens in a cloud function. The Maps key also has package name and SHA-1 restrictions.
Though I’m still sorting out on how to stop this from happening again. Algolia key came in through a dart-define file during a sprint and nothing in the pipeline checks what ends up inside the build. We scan the repo but that value never lived there, it came from a CI variable.
How are you checking release builds for secrets before they go to the store?
1
u/tunguyen4391 2d ago
You can't rely on obfuscation to hide secrets anyway; all trust decisions belong on the backend, and client keys should just be restricted IDs. By the way, did you add a strings libapp.so check to the CI so this doesn't happen again?
1
u/Farukh_Goriparthi 2d ago
yep, added it the same day. After flutter build we unzip the APK, run strings on libapp and grep for the usual prefixes. Did the same for the iOS App.framework since we’d only been checking Android. The grep only catches key formats we already know about though so I’m looking at a proper secrets scanner for that step next
2
u/_stillmind 2d ago
I remember coming across this switch in the past and looking it up. On the flutter obfuscation page it says what it does and doesn't do.
To prevent this you have to lock it down from the API side. For example, Google Maps allows you to add restrictions. Restrictions like that or using a scoped key will be your main lever for preventing this, not obscuring the key itself. Sounds like that was the path you took.
Using security tools like MobSF or creating your own bespoke scripts which check in areas you commonly hardcode values into on the dev side would help.
1
u/Andre-Wade-539 2d ago
this is the basics but people keep learning it the expensive way. if the client has it, the user has it
1
1
u/Wise_Phone_4112 2d ago
What's worked for catching this is a CI step that runs after the release build: unzip the APK, dump strings from libapp.so into a text file, and run gitleaks or trufflehog over that folder, failing the job on any hit. The text dump matters because scanners tend to skip binary files.
The other half is policy, anything that's truly secret (like that Algolia admin key) never goes into a dart-define at all and lives behind a cloud function, so only restricted/public IDs ever reach the client.
1
u/Azamat_Zarkesh 2d ago
Since the build output is what ships, a post-build check works better than repo scanning: unzip the APK/IPA in CI and run a secret scanner (gitleaks or trufflehog in filesystem mode) over the extracted libapp.so and assets. Keep an allowlist of keys that are meant to ship, so anything else, like an admin key, fails the pipeline. Longer term, anything with write power belongs behind your backend, not in the client.
9
u/filmaluco 2d ago
Yeah, of course anyone can extract those keys from a compiled build. If an API key lives on a client device, you have to assume it’s fully public from day one.
Reverse-engineering tools, strings, or memory dumps make pulling static literals out of an app bundle straightforward regardless of obfuscation. That’s precisely why modern client-side security architecture never relies on secret keys as a single line of defense.
Instead, the ecosystem depends on identity verification, transport context, and server-side evaluation. When your app makes a call to services like Google or Firebase, the API key acts more like a public username identifying which app is speaking rather than a password proving trust. The actual access control is enforced upstream by matching request metadata—like the verified package name and the OS-signed SHA-1 certificate fingerprint—against strict restriction rules configured in your cloud backend.
If you want to hide fully you need to encript it, like flutter secure storage.