Skip to content

[flutter_local_notifications] verify that notification intents on Android come from the plugin - #2849

Open
brlumen wants to merge 1 commit into
MaikuB:masterfrom
brlumen:fix/android-verify-notification-intents
Open

brlumen wants to merge 1 commit into
MaikuB:masterfrom
brlumen:fix/android-verify-notification-intents

Conversation

@brlumen

@brlumen brlumen commented Oct 10, 2026

Copy link
Copy Markdown

Fixes #2848.

On Android, any app can start the launch activity with a SELECT_NOTIFICATION or SELECT_FOREGROUND_NOTIFICATION intent, and the plugin reports it as a notification response with the payload, action id and other details taken from the intent. This PR makes the plugin sign the intents of the notifications it shows and ignore intents without a valid signature.

Changes

  • New NotificationIntentSigner. It computes an HMAC-SHA256 of the intent's action and the extras the plugin reads: notification id, tag, action id, cancelNotification and payload. The key is 32 random bytes from SecureRandom, created on first use and stored in getNoBackupFilesDir(), so it isn't included in backups.
  • createNotification() signs the intent for tapping the notification and the intents for actions with showsUserInterface: true. These are the only intents that go to the launch activity. Scheduled and foreground service notifications are built through createNotification() too, so they are signed as well. Actions handled by ActionBroadcastReceiver aren't affected, because the receiver isn't exported.
  • getNotificationAppLaunchDetails(), onNewIntent() and onAttachedToActivity() accept an intent only if its signature is valid. Otherwise the launch is treated as a normal one: didNotificationLaunchApp is false, onDidReceiveNotificationResponse isn't called, no notification is cancelled, and a warning is logged.
  • New AndroidInitializationSettings.verifyNotificationIntents, true by default. Setting it to false restores the old behaviour, for apps that start the launch activity with these intents themselves. The value is saved in the plugin's shared preferences on initialize(), so it's also available when the plugin handles the launch intent before initialize() is called.

The reply text of an action with inputs isn't covered by the signature. The system adds it to the intent when the user replies, and other apps can't attach it to a signed intent because they can't get one.

Compatibility

Notifications shown by an earlier version of the plugin aren't signed. If one of them is still showing after the app is updated, tapping it opens the app without the payload. On the device I tested, installing the update removed the app's notifications, so this didn't come up there.

Because of this and the new default, I think the change fits a major release such as 23.0.0. I haven't added a changelog entry, since you usually write those. I'm happy to add one, or a section in the readme, if you'd like.

Testing

  • Added NotificationIntentSignerTest (Robolectric, 11 tests). A signed intent verifies. An unsigned intent, an intent with a forged signature, and a signed intent with any signed field changed or removed don't.
  • Updated the Dart test for initialize and added one for verifyNotificationIntents: false.
  • melos run analyze, flutter test in flutter_local_notifications, and ./gradlew flutter_local_notifications:testDebug from the example app pass.
  • Checked the example app on a TECNO KM7k with Android 15:
    • On master, the steps from the issue open "Second Screen" with the forged payload, both when the app is running and on a cold start. A forged SELECT_FOREGROUND_NOTIFICATION intent delivers the forged action id, and with cancelNotification it cancels the notification with the given id.
    • With this change, the same intents open the home page ("Did notification launch app? false"), a warning is logged, and the notification stays.
    • Tapping a real notification delivers its payload while the app is running and on a cold start after the process was killed. Tapping "Action 3" (showsUserInterface: true) delivers id_3 and the payload.
    • With verifyNotificationIntents: false, the forged intents are accepted again, both when the app is running and on a cold start.

…roid come from the plugin

Other apps can start the launch activity with SELECT_NOTIFICATION or
SELECT_FOREGROUND_NOTIFICATION intents, and the plugin reported them as
notification responses. The plugin now signs the intents of the
notifications it shows with an HMAC keyed by a per-install secret and
ignores intents without a valid signature. Verification can be turned off
with AndroidInitializationSettings.verifyNotificationIntents.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] Other apps can forge notification responses by starting the launch activity

1 participant