Using Javascript interfaces in WebViews is unsafe as it allows JavaScript to invoke Java methods,
potentially giving attackers access to data or sensitive app functionality. WebViews might include
untrusted sources such as third-party iframes, making this functionality particularly risky. As
Javascript interfaces are passed to every frame in the WebView, those iframes are also able to
access the exposed Java methods.

== Ask Yourself Whether

* The content in the WebView is fully trusted and secure.
* Potentially untrusted iframes could be loaded in the WebView.
* The Javascript interface has to be exposed for the entire lifecycle of the WebView.
* The exposed Java methods will accept input from potentially untrusted sources.

There is a risk if you answered yes to any of these questions.

== Recommended Secure Coding Practices

=== Disable JavaScript

If it is possible to disable JavaScript in the WebView, this is the most secure option. By default,
JavaScript is disabled in a WebView, so you do not need to explicitly call
``webSettings.setJavaScriptEnabled(true)`` in your ``WebSettings`` configuration. Of course, sometimes
it is necessary to enable JavaScript, in which case the following recommendations should be considered.

=== Remove JavaScript interface when loading untrusted content

JavaScript interfaces can be removed at a later point. It is recommended to remove the JavaScript
interface when it is no longer needed. If it is needed for a longer time, consider removing it before
loading untrusted content. This can be done by calling ``webView.removeJavascriptInterface("interfaceName")``.

A good place to do this is inside the ``shouldInterceptRequest`` method of a ``WebViewClient``, where you can
check the URL or resource being loaded and remove the interface if the content is untrusted.

=== Alternative methods to implement native bridges

If a native bridge has to be added to the WebView, and it is impossible to remove it at a later point,
consider using an alternative method that offers more control over the communication flow.
``WebViewCompat.postWebMessage``/``WebViewCompat.addWebMessageListener`` and ``WebMessagePort.postMessage``
offer more ways to validate incoming and outgoing messages, such as by being able to restrict the origins
that can send messages to the JavaScript bridge.

== Sensitive Code Example

[source,kotlin]
----
class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val webView = WebView(this)
        webView.settings.javaScriptEnabled = true
        webView.addJavascriptInterface(JavaScriptBridge(), "androidBridge")  // Sensitive
    }

    inner class JavaScriptBridge {
        @JavascriptInterface
        fun accessUserData(userId): String {
            return getUserData(userId)
        }
    }
}
----

== Compliant Solution

The most secure option is to disable JavaScript entirely.

[source,kotlin]
----
class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val webView = WebView(this)
        webView.settings.javaScriptEnabled = false
    }
}
----

If possible, remove the JavaScript interface after it is no longer needed, or before loading any untrusted content.

[source,kotlin]
----
class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val webView = WebView(this)
        webView.settings.javaScriptEnabled = true

        webView.addJavascriptInterface(JavaScriptBridge(), "androidBridge")

        // Sometime later, before unsafe content is loaded, remove the JavaScript interface
        webView.removeJavascriptInterface("androidBridge")
    }
}
----

If a JavaScript bridge must be used, consider using ``WebViewCompat.addWebMessageListener`` instead. This allows you to restrict the origins that can send messages to the JavaScript bridge.

[source,kotlin]
----
class ExampleActivity : AppCompatActivity() {
    private val ALLOWED_ORIGINS = setOf("https://example.com")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val webView = WebView(this)
        webView.settings.javaScriptEnabled = true

        WebViewCompat.addWebMessageListener(
            webView, "androidBridge", ALLOWED_ORIGINS,  // Only allow messages from these origins
            object : WebViewCompat.WebMessageListener {
                override fun onPostMessage(
                    view: WebView,
                    message: WebMessageCompat,
                    sourceOrigin: Uri,
                    isMainFrame: Boolean,
                    replyProxy: JavaScriptReplyProxy
                ) {
                    // Handle the message
                }
            }
        )
    }
}
----

== See

* Android Documentation - https://developer.android.com/privacy-and-security/risks/insecure-webview-native-bridges[Insecure WebView native bridges]
* Android Documentation - https://developer.android.com/reference/androidx/webkit/WebViewCompat[WebViewCompat API reference]
* OWASP - https://owasp.org/Top10/A05_2021-Security_Misconfiguration/[Top 10 2021 Category A5 - Security Misconfiguration]
* OWASP - https://owasp.org/www-project-mobile-top-10/2023-risks/m4-insufficient-input-output-validation.html[Mobile Top 10 2024 Category M4 - Insufficient Input/Output Validation]
* OWASP - https://owasp.org/www-project-mobile-top-10/2023-risks/m8-security-misconfiguration.html[Mobile Top 10 2024 Category M8 - Security Misconfiguration]
* CWE - https://cwe.mitre.org/data/definitions/79[CWE-79 - Improper Neutralization of Input During Web Page Generation]
