From bcb740695b532c59070e45c6dbd34bc3fee6d4f7 Mon Sep 17 00:00:00 2001 From: f321x Date: Wed, 27 Aug 2025 17:28:59 +0200 Subject: [PATCH] lnurl: only sanitize untrusted LNURL errors it is not neccessary to sanitize all LNURLErrors as most are just source strings. --- electrum/lnurl.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/electrum/lnurl.py b/electrum/lnurl.py index 3f035bae1..9bfd0592f 100644 --- a/electrum/lnurl.py +++ b/electrum/lnurl.py @@ -10,22 +10,30 @@ import urllib.parse import aiohttp.client_exceptions -from electrum import segwit_addr +from electrum import segwit_addr, util from electrum.segwit_addr import bech32_decode, Encoding, convertbits, bech32_encode from electrum.lnaddr import LnDecodeException, LnEncodeException from electrum.network import Network from electrum.logging import get_logger +from electrum.i18n import _ _logger = get_logger(__name__) -class LNURLError(Exception): - def __init__(self, message="", *args): - # error messages are returned by the LNURL server, some services could try to trick - # users into doing something by sending a malicious error message - modified_message = f"[DO NOT TRUST THIS MESSAGE]:\n{message}" - super().__init__(modified_message, *args) +class LNURLError(Exception): pass + +class UntrustedLNURLError(LNURLError): + def __init__(self, message=""): + # use if error messages are returned by the LNURL server, + # some services could try to trick users into doing something + # by sending a malicious error message + if message: + message = ( + f"{_('[DO NOT TRUST THIS MESSAGE]:')}\n" + f"{util.error_text_str_to_safe_str(message)}" + ) + super().__init__(message) def decode_lnurl(lnurl: str) -> str: @@ -124,7 +132,7 @@ async def _request_lnurl(url: str) -> dict: status = response.get("status") if status and status == "ERROR": - raise LNURLError(f"LNURL request encountered an error: {response.get('reason', '')}") + raise UntrustedLNURLError(f"LNURL request encountered an error: {response.get('reason', '')}") return response @@ -168,7 +176,7 @@ def _parse_lnurl3_response(lnurl_response: dict) -> LNURL3Data: """Parses the server response received when requesting a LNURL-withdraw (lud3) request""" callback_url = _parse_lnurl_response_callback_url(lnurl_response) if not (k1 := lnurl_response.get('k1')): - raise LNURLError(f"Missing k1 value in LNURL3 response: {lnurl_response=}") + raise UntrustedLNURLError(f"Missing k1 value in LNURL3 response: {lnurl_response=}") default_description = lnurl_response.get('defaultDescription', '') try: min_withdrawable_sat = int(lnurl_response['minWithdrawable']) // 1000 @@ -194,7 +202,7 @@ async def request_lnurl(url: str) -> LNURLData: return _parse_lnurl6_response(lnurl_dict) elif tag == 'withdrawRequest': return _parse_lnurl3_response(lnurl_dict) - raise LNURLError(f"Unknown subtype of lnurl. tag={tag}") + raise UntrustedLNURLError(f"Unknown subtype of lnurl. tag={tag}") async def try_resolve_lnurlpay(lnurl: Optional[str]) -> Optional[LNURL6Data]: @@ -236,7 +244,7 @@ async def callback_lnurl(url: str, params: dict) -> dict: status = response.get("status") if status and status == "ERROR": - raise LNURLError(f"LNURL request encountered an error: {response.get('reason', '')}") + raise UntrustedLNURLError(f"LNURL request encountered an error: {response.get('reason', '')}") # TODO: handling of specific errors (validate fields, e.g. for lnurl6) return response