Medium firefox Spoofing 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionSpoofing issue in the WebAuthn component in Firefox for Android
ComponentCore
Bug ClassSpoofing
Tracker1988145
Fix commit8cf3589f064d (firefox) +36/-14
CISA KEVNot listed
Creditedhafiizh & kang ali
Disclosed2026-02-24

Changed Functions

FunctionChangeNotes
AlwaysDeniedSchemes
mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AlwaysDeniedSchemes.kt
modified
AppLinksInterceptor
mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksInterceptor.kt
modified
AppLinksUseCases
mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt
modified

Files Changed

  • mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AlwaysDeniedSchemes.kt
  • mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksInterceptor.kt
  • mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt
  • mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt
  • mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt
diff --git a/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AlwaysDeniedSchemes.kt b/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AlwaysDeniedSchemes.kt
new file mode 100644
index 00000000000..9584e720897
--- /dev/null
+++ b/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AlwaysDeniedSchemes.kt
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package mozilla.components.feature.app.links
+
+import java.util.Locale
+
+/**
+ * Utility class whether scheme is allowed or denied.
+ */
+class AlwaysDeniedSchemes(private val schemes: Set<String>) {
+    /**
+     * Whether or not we should deny given URI scheme by dangerous etc.
+     *
+     * @param scheme A scheme of URI
+     * @return `true` If scheme should be denied
+     */
+    fun shouldDeny(scheme: String?): Boolean {
+         return schemes.contains(scheme?.lowercase(Locale.ROOT))
+    }
+}
diff --git a/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksInterceptor.kt b/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksInterceptor.kt
index 96512c2e113..35160e4d497 100644
--- a/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksInterceptor.kt
+++ b/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksInterceptor.kt
@@ -60,7 +60,7 @@ private val ALLOWED_SCHEMES_IN_SUBFRAME: List<String> = listOf(
 class AppLinksInterceptor(
     private val context: Context,
     private val engineSupportedSchemes: Set<String> = ENGINE_SUPPORTED_SCHEMES,
-    private val alwaysDeniedSchemes: Set<String> = ALWAYS_DENY_SCHEMES,
+    private val alwaysDeniedSchemes: AlwaysDeniedSchemes = AlwaysDeniedSchemes(ALWAYS_DENY_SCHEMES),
     private var launchInApp: () -> Boolean = { false },
     private val useCases: AppLinksUseCases = AppLinksUseCases(
         context,
@@ -102,7 +102,7 @@ class AppLinksInterceptor(
             // If scheme not in supported list then follow user preference
             !launchInApp() && !isPossibleAuthentication(tabSessionState) && engineSupportsScheme -> true
             // Never go to an external app when scheme is in blocklist
-            alwaysDeniedSchemes.contains(uriScheme) -> true
+            alwaysDeniedSchemes.shouldDeny(uriScheme) -> true
             else -> false
         }
 
diff --git a/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt b/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt
index 8b136f3f1ed..b851ed978f2 100644
--- a/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt
+++ b/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt
@@ -52,7 +52,7 @@ private const val ANDROID_RESOLVER_PACKAGE_NAME = "android"
 class AppLinksUseCases(
     private val context: Context,
     private var launchInApp: () -> Boolean = { false },
-    private val alwaysDeniedSchemes: Set<String> = ALWAYS_DENY_SCHEMES,
+    private val alwaysDeniedSchemes: AlwaysDeniedSchemes = AlwaysDeniedSchemes(ALWAYS_DENY_SCHEMES),
     private val installedBrowsers: Browsers = BrowsersCache.all(context),
 ) {
     @Suppress(
@@ -159,7 +159,7 @@ class AppLinksUseCases(
 
             val appIntent = when {
                 intent?.data == null -> null
-                alwaysDeniedSchemes.contains(intent.data?.scheme) -> null
+                alwaysDeniedSchemes.shouldDeny(intent.data?.scheme) -> null
                 else -> intent
             }
 
@@ -249,7 +249,7 @@ class AppLinksUseCases(
             appIntent?.let {
                 try {
                     val scheme = appIntent.data?.scheme
-                    if (scheme != null && alwaysDeniedSchemes.contains(scheme)) {
+                    if (alwaysDeniedSchemes.shouldDeny(scheme)) {
                         return
                     }
 
diff --git a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt
index eb0b5cfb7b3..d3e1aa66794 100644
--- a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt
+++ b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt
@@ -202,7 +202,7 @@ class AppLinksInterceptorTest {
         val blocklistedScheme = "blocklisted"
         val feature = AppLinksInterceptor(
             context = mockContext,
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { true },
             useCases = mockUseCases,
         )
@@ -259,7 +259,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -279,7 +279,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(notSupportedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(notSupportedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -300,7 +300,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { true },
             useCases = mockUseCases,
         )
@@ -321,7 +321,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -343,7 +343,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -594,7 +594,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { true },
             useCases = mockUseCases,
         )
@@ -616,7 +616,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
diff --git a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt
index 9614cf363e0..a4de61e4d4a 100644
--- a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt
+++ b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt
@@ -357,7 +357,7 @@ class AppLinksUseCasesTest {
     fun `A intent scheme denied should return no app intent`() {
         val uri = "intent://details/#Intent"
         val context = createContext(Triple(uri, appPackage, ""))
-        val subject = AppLinksUseCases(context, { true }, alwaysDeniedSchemes = setOf("intent"))
+        val subject = AppLinksUseCases(context, { true }, alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf("intent")))
 
         val redirect = subject.interceptedAppLinkRedirect.invoke(uri)
 
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt
index eb0b5cfb7b3..d3e1aa66794 100644
--- a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt
+++ b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt
@@ -202,7 +202,7 @@ class AppLinksInterceptorTest {
         val blocklistedScheme = "blocklisted"
         val feature = AppLinksInterceptor(
             context = mockContext,
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { true },
             useCases = mockUseCases,
         )
@@ -259,7 +259,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -279,7 +279,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(notSupportedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(notSupportedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -300,7 +300,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { true },
             useCases = mockUseCases,
         )
@@ -321,7 +321,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -343,7 +343,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -594,7 +594,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { true },
             useCases = mockUseCases,
         )
@@ -616,7 +616,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
diff --git a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt
index 9614cf363e0..a4de61e4d4a 100644
--- a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt
+++ b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt
@@ -357,7 +357,7 @@ class AppLinksUseCasesTest {
     fun `A intent scheme denied should return no app intent`() {
         val uri = "intent://details/#Intent"
         val context = createContext(Triple(uri, appPackage, ""))
-        val subject = AppLinksUseCases(context, { true }, alwaysDeniedSchemes = setOf("intent"))
+        val subject = AppLinksUseCases(context, { true }, alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf("intent")))
 
         val redirect = subject.interceptedAppLinkRedirect.invoke(uri)
Loading diff…