From faeabc7ae4f3a14873c61d9fb92fe1bcd1b7bf02 Mon Sep 17 00:00:00 2001 From: OS-pedrogustavobilro Date: Mon, 7 Sep 2026 17:29:00 +0100 Subject: [PATCH 1/2] fix(share): Add nonce validation to prevent receiver spoofing Validates nonce to prevent other apps from spoofing the chosen component via exported receiver. References: https://github.com/ionic-team/capacitor-plugins/issues/2591 --- .../plugins/share/SharePlugin.java | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java b/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java index dcfa33831..f93a09012 100644 --- a/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java +++ b/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java @@ -19,25 +19,43 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import org.json.JSONException; @CapacitorPlugin(name = "Share") public class SharePlugin extends Plugin { + private static final String NONCE_EXTRA_KEY = "_share_nonce"; + private BroadcastReceiver broadcastReceiver; private boolean stopped = false; private boolean isPresenting = false; private ComponentName chosenComponent; + private String expectedNonce; @Override public void load() { broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + // Validate nonce to prevent spoofing from other apps + String receivedNonce = intent.getStringExtra(NONCE_EXTRA_KEY); + if (receivedNonce == null || !receivedNonce.equals(expectedNonce)) { + return; // Reject broadcasts that don't have the correct nonce + } + + // Extract chosen component + ComponentName component; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - chosenComponent = intent.getParcelableExtra(Intent.EXTRA_CHOSEN_COMPONENT, ComponentName.class); + component = intent.getParcelableExtra(Intent.EXTRA_CHOSEN_COMPONENT, ComponentName.class); } else { - chosenComponent = getParcelableExtraLegacy(intent, Intent.EXTRA_CHOSEN_COMPONENT); + component = getParcelableExtraLegacy(intent, Intent.EXTRA_CHOSEN_COMPONENT); + } + + // Only clear nonce if we successfully got the component data + if (component != null) { + chosenComponent = component; + expectedNonce = null; } } }; @@ -64,6 +82,7 @@ private void activityResult(PluginCall call, ActivityResult result) { call.resolve(callResult); } isPresenting = false; + expectedNonce = null; } @PluginMethod @@ -117,6 +136,12 @@ public void share(PluginCall call) { if (files != null && files.length() != 0) { shareFiles(files, intent, call); } + + // Generate a random nonce to prevent spoofing via exported receiver + expectedNonce = UUID.randomUUID().toString(); + Intent callbackIntent = new Intent(Intent.EXTRA_CHOSEN_COMPONENT); + callbackIntent.putExtra(NONCE_EXTRA_KEY, expectedNonce); + int flags = PendingIntent.FLAG_UPDATE_CURRENT; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { flags = flags | PendingIntent.FLAG_MUTABLE; @@ -126,7 +151,7 @@ public void share(PluginCall call) { } // requestCode parameter is not used. Providing 0 - PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, new Intent(Intent.EXTRA_CHOSEN_COMPONENT), flags); + PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, callbackIntent, flags); Intent chooser = Intent.createChooser(intent, dialogTitle, pi.getIntentSender()); chosenComponent = null; chooser.addCategory(Intent.CATEGORY_DEFAULT); @@ -180,6 +205,7 @@ private void shareFiles(JSArray files, Intent intent, PluginCall call) { @Override protected void handleOnDestroy() { + expectedNonce = null; if (broadcastReceiver != null) { getActivity().unregisterReceiver(broadcastReceiver); } From 1a1d02779681f6e4ef3e910dc6df99a23f46a696 Mon Sep 17 00:00:00 2001 From: OS-pedrogustavobilro Date: Mon, 7 Sep 2026 18:22:10 +0100 Subject: [PATCH 2/2] chore: correct code comments --- .../main/java/com/capacitorjs/plugins/share/SharePlugin.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java b/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java index f93a09012..9b97dc013 100644 --- a/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java +++ b/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java @@ -39,12 +39,13 @@ public void load() { @Override public void onReceive(Context context, Intent intent) { // Validate nonce to prevent spoofing from other apps + // Reference: https://github.com/ionic-team/capacitor-plugins/pull/2592 String receivedNonce = intent.getStringExtra(NONCE_EXTRA_KEY); if (receivedNonce == null || !receivedNonce.equals(expectedNonce)) { - return; // Reject broadcasts that don't have the correct nonce + // Reject broadcasts that don't have the correct nonce + return; } - // Extract chosen component ComponentName component; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { component = intent.getParcelableExtra(Intent.EXTRA_CHOSEN_COMPONENT, ComponentName.class);