DEV Community

Davis | UNTRAPD
Davis | UNTRAPD

Posted on

A Flutter Plugin Secretly Injected Permissions Into My App — Google Rejected It 4 Times Before I Found It

Google Play rejected my Flutter app 4 times. Same reason each time: sensitive permissions I never asked for.

I grepped every file. READ_SMS, SEND_SMS, READ_CALL_LOG — none of them in my AndroidManifest.xml.

Because they weren't in my manifest. They were in my compiled bytecode.

The culprit

permission_handler. One of the most popular Flutter plugins. 8,000+ likes on pub.dev.

Here's what nobody tells you: it compiles string constants for every Android permission it supports into your DEX bytecode. Not because you request them. Not because you use them. Because the plugin's internal mapping references them all.

Google's automated review doesn't just read your manifest. It scans compiled bytecode for permission-related strings. permission_handler puts them there. Every single one.

You didn't request them. You don't use them. Google still flags them.

The trap nobody warns you about

Here's what made this worse: Google Play testing tracks don't enforce the same permission policies as Production.

I uploaded builds with SMS permissions to Internal Testing. Approved. Closed Testing. Approved. Pre-registration. Approved. 172 countries. Everything green.

Then I submitted to Production. Rejected.

The testing tracks let everything through. You build confidence. You think your permissions are fine. Then Production applies a completely different set of rules — and you're back to zero with no warning.

If you're testing sensitive permissions on Internal or Closed Testing and it "works" — that tells you nothing about Production approval.

What didn't work

  • Removing permissions from AndroidManifest.xml — scanner reads DEX, not just manifest
  • tools:node="remove" — removes from merged manifest, strings survive in bytecode
  • ProGuard/R8 rules — some references survive optimization
  • Permission declaration form — Google rejected it because I wasn't using those permissions

What worked

Removed permission_handler entirely. Replaced with a native Kotlin method channel — one file, exactly the permissions I need, zero baggage.

Next submission: zero phantom permissions. Approved.

The lesson

Two things bit me at once:

  1. Flutter plugins have compiled consequences. permission_handler ships references to every Android permission into your binary. Google can't tell the difference between "string exists in code" and "app uses this permission."

  2. Testing tracks are not Production. Don't assume approval on Internal/Closed Testing means anything for Production. The policy enforcement is different.

Two commands that would have saved me two weeks:

# What you installed
flutter pub deps

# What actually shipped
apkanalyzer dex packages your-app.apk | grep permission
Enter fullscreen mode Exit fullscreen mode

They don't show the same thing.

The timeline

4 rejections over 5 days. 1 plugin. Now live on Google Play in 172 countries.


Has anyone else been bitten by this? I'm curious — does Google intentionally skip permission checks on testing tracks, or is it a gap in their review system? And is permission_handler the only Flutter plugin that does this?

Top comments (0)