diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/metamask/transport/MetamaskResponder.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/metamask/transport/MetamaskResponder.kt index 709292ae..b966985d 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/metamask/transport/MetamaskResponder.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/metamask/transport/MetamaskResponder.kt @@ -4,8 +4,12 @@ import android.util.Log import io.novafoundation.nova.common.utils.LOG_TAG import io.novafoundation.nova.feature_dapp_impl.web3.webview.WebViewHolder import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import org.json.JSONObject -sealed class MetamaskError(val errorCode: Int, message: String) : Throwable(message) { +/** + * [publicMessage] is sent to the dApp page as is, so it must never carry internal error details. + */ +sealed class MetamaskError(val errorCode: Int, val publicMessage: String) : Throwable(publicMessage) { class Rejected : MetamaskError(4001, "Action rejected") @@ -33,7 +37,8 @@ class MetamaskResponder(private val webViewHolder: WebViewHolder) { } fun respondError(messageId: String, error: MetamaskError) { - val js = "window.ethereum.sendRpcError($messageId, ${error.errorCode}, \"${error.message}\")" + val message = JSONObject.quote(error.publicMessage) + val js = "window.ethereum.sendRpcError($messageId, ${error.errorCode}, $message)" evaluateJs(js) } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/PolkadotJsResponder.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/PolkadotJsResponder.kt index 38ed5396..b9226b66 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/PolkadotJsResponder.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/PolkadotJsResponder.kt @@ -3,7 +3,9 @@ package io.novafoundation.nova.feature_dapp_impl.web3.polkadotJs import android.util.Log import io.novafoundation.nova.common.utils.LOG_TAG import io.novafoundation.nova.feature_dapp_impl.web3.Web3Responder +import io.novafoundation.nova.feature_dapp_impl.web3.states.Web3StateMachineHost import io.novafoundation.nova.feature_dapp_impl.web3.webview.WebViewHolder +import org.json.JSONObject class PolkadotJsResponder( private val webViewHolder: WebViewHolder @@ -35,5 +37,20 @@ class PolkadotJsResponder( private fun successSubscription(id: String, result: String) = "window.walletExtension.onAppSubscription(\"$id\", $result)" - private fun failure(id: String, error: Throwable) = "window.walletExtension.onAppResponse(\"$id\", null, new Error(\"${error.message.orEmpty()}\"))" + private fun failure(id: String, error: Throwable): String { + val message = JSONObject.quote(publicErrorMessage(error)) + + return "window.walletExtension.onAppResponse(\"$id\", null, new Error($message))" + } + + /** + * The page is untrusted, so it only ever receives a fixed message per known rejection reason, + * never the text of an arbitrary internal exception. + */ + private fun publicErrorMessage(error: Throwable): String = when (error) { + Web3StateMachineHost.NotAuthorizedException -> "Rejected by user" + Web3StateMachineHost.SigningFailedException -> "Signing failed" + Web3StateMachineHost.PhishingDetectedException -> "Phishing detected!" + else -> "Request failed" + } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/states/PhishingDetectedPolkadotJsState.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/states/PhishingDetectedPolkadotJsState.kt index bd5bcaed..a81c37aa 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/states/PhishingDetectedPolkadotJsState.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/polkadotJs/states/PhishingDetectedPolkadotJsState.kt @@ -2,10 +2,11 @@ package io.novafoundation.nova.feature_dapp_impl.web3.polkadotJs.states import io.novafoundation.nova.feature_dapp_impl.web3.polkadotJs.PolkadotJsTransportRequest import io.novafoundation.nova.feature_dapp_impl.web3.states.PhishingDetectedState +import io.novafoundation.nova.feature_dapp_impl.web3.states.Web3StateMachineHost class PhishingDetectedPolkadotJsState : PhishingDetectedState, PolkadotJsState>(), PolkadotJsState { override fun rejectError(): Throwable { - return IllegalAccessException("Phishing detected!") + return Web3StateMachineHost.PhishingDetectedException } } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/states/Web3ExtensionStateMachine.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/states/Web3ExtensionStateMachine.kt index bd68f700..05b054cb 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/states/Web3ExtensionStateMachine.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/web3/states/Web3ExtensionStateMachine.kt @@ -42,6 +42,7 @@ interface Web3StateMachineHost { object NotAuthorizedException : Exception("Rejected by user") object SigningFailedException : Exception("Signing failed") + object PhishingDetectedException : Exception("Phishing detected!") val selectedAccount: Flow val currentPageAnalyzed: Flow