What changed, and why it matters
This commit makes the Electrum desktop wallet explicitly tell its Qt GUI widgets to interpret certain text as rich HTML-like text, rather than leaving it on the automatic/default setting. In recent Qt versions, the default behavior changed so that labels with link tags or HTML may no longer render as clickable rich text unless the application opts in. The patch is defensive: it ensures user-facing labels (help text, warnings, update notices, transaction details, seed warnings, etc.) continue to display formatted text and clickable links correctly. There is no direct evidence in the diff of an exploitable vulnerability being fixed; it reads more like a compatibility/adjustment change after a Qt behavior change. One label was also converted from HTML line breaks to plain-text line breaks, which slightly reduces HTML parsing exposure.
Treat as a routine UI compatibility/maintenance patch. Review whether any of the now-explicit RichText labels display attacker-influenced strings (e.g., transaction memos, external messages, error values) and ensure those inputs are sanitized or escaped before being set as rich text. Monitor the project for any follow-up security advisory or CVE that may later be associated with this Qt behavior change.
Security signals we found
Explicit RichText opt-in across many Qt labels after Qt behavior change
One HTML-rich label converted to plain-text formatting (Revealer calibration dialog)
No input validation or escaping changes observed
No vendor security disclosure, CVE, or researcher attribution in commit
Evidence from the diff
The change adds setTextFormat(Qt.TextFormat.RichText) to numerous QLabel and QTextEdit instances across the Qt GUI. Qt 6.4+ changed QLabel’s default text format handling so that RichText is not automatically inferred from content in some contexts. The patch explicitly opts in where the application already constructs HTML-style strings or relies on rich-text rendering. Affected areas include crash reporter, password dialogs, seed dialogs, transaction details, update checker, wizard titles, plugin dialogs (Coldcard, Revealer, TrustedCoin), and a generic HelpLabel. The Revealer calibration dialog additionally replaces HTML
tags with plain-text newlines for one label. No input sanitization changes, no CVE reference, and no security disclosure are present in the commit or supplied references.
Changed components
electrum/gui/qt/channel_details.pyelectrum/gui/qt/console.pyelectrum/gui/qt/exception_window.pyelectrum/gui/qt/history_list.pyelectrum/gui/qt/password_dialog.pyelectrum/gui/qt/seed_dialog.pyelectrum/gui/qt/transaction_dialog.pyelectrum/gui/qt/update_checker.pyelectrum/gui/qt/util.pyelectrum/gui/qt/wizard/wizard.pyelectrum/plugins/coldcard/qt.pyelectrum/plugins/revealer/qt.pyelectrum/plugins/trustedcoin/qt.pyInspect captured patch +29 / −6
### electrum/gui/qt/channel_details.py
@@ -3,6 +3,7 @@
import PyQt6.QtGui as QtGui
import PyQt6.QtWidgets as QtWidgets
import PyQt6.QtCore as QtCore
+from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QLabel, QHBoxLayout
from electrum.util import ShortID
@@ -33,6 +34,7 @@ class LinkedLabel(QtWidgets.QLabel):
def __init__(self, text, on_clicked):
super().__init__(text)
self.linkActivated.connect(on_clicked)
+ self.setTextFormat(Qt.TextFormat.RichText)
class ChannelDetailsDialog(QtWidgets.QDialog, MessageBoxMixin, QtEventListener):
### electrum/gui/qt/console.py
@@ -37,6 +37,7 @@ def __init__(self, text, parent):
self.setMargin(0)
parent.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setWordWrap(True)
+ self.setTextFormat(Qt.TextFormat.RichText)
def mousePressEvent(self, e):
self.hide()
### electrum/gui/qt/exception_window.py
@@ -60,6 +60,7 @@ def __init__(self, config: 'SimpleConfig', exctype, value, tb):
main_box = QVBoxLayout()
heading = QLabel('<h2>' + BaseCrashReporter.CRASH_TITLE + '</h2>')
+ heading.setTextFormat(Qt.TextFormat.RichText)
main_box.addWidget(heading)
main_box.addWidget(QLabel(BaseCrashReporter.CRASH_MESSAGE))
@@ -210,7 +211,7 @@ def __init__(self, *, parent: QWidget, text: str):
report_text = QLabel(text)
report_text.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
- report_text.setTextFormat(Qt.TextFormat.AutoText) # likely rich text
+ report_text.setTextFormat(Qt.TextFormat.RichText)
scroll_area.setWidget(report_text)
vbox.addWidget(scroll_area)
### electrum/gui/qt/history_list.py
@@ -627,7 +627,9 @@ def show_summary(self):
d.setMinimumSize(600, 150)
vbox = QVBoxLayout()
msg = messages.to_rtf(messages.MSG_CAPITAL_GAINS)
- vbox.addWidget(WWLabel(msg))
+ lbl = WWLabel(msg)
+ lbl.setTextFormat(Qt.TextFormat.RichText)
+ vbox.addWidget(lbl)
grid = QGridLayout()
grid.addWidget(QLabel(_("Begin")), 0, 1)
grid.addWidget(QLabel(_("End")), 0, 2)
### electrum/gui/qt/password_dialog.py
@@ -121,6 +121,7 @@ def __init__(self, msg, kind, OK_button, wallet=None):
# Password Strength Label
if kind != PW_PASSPHRASE:
self.pw_strength = QLabel()
+ self.pw_strength.setTextFormat(Qt.TextFormat.RichText)
grid.addWidget(self.pw_strength, 3, 0, 1, 2)
self.new_pw.textChanged.connect(self.pw_changed)
@@ -294,6 +295,7 @@ def __init__(self, parent=None, msg=None):
WindowModalDialog.__init__(self, parent, _("Enter Password"))
self.pw = pw = PasswordLineEdit()
label = QLabel(msg)
+ label.setTextFormat(Qt.TextFormat.RichText)
label.setWordWrap(True)
vbox = QVBoxLayout()
vbox.addWidget(label)
### electrum/gui/qt/seed_dialog.py
@@ -180,8 +180,10 @@ def __init__(
vbox.addStretch(1)
self.seed_status = WWLabel('')
+ self.seed_status.setTextFormat(Qt.TextFormat.RichText)
vbox.addWidget(self.seed_status)
self.seed_warning = WWLabel('')
+ self.seed_warning.setTextFormat(Qt.TextFormat.RichText)
if msg:
self.seed_warning.setText(seed_warning_msg(seed))
else:
### electrum/gui/qt/transaction_dialog.py
@@ -1019,6 +1019,7 @@ def add_tx_stats(self, vbox):
fee_hbox = QHBoxLayout()
self.fee_label = TxDetailLabel()
+ self.fee_label.setTextFormat(Qt.TextFormat.RichText)
fee_hbox.addWidget(self.fee_label)
self.fee_warning_icon = QLabel()
pixmap = QPixmap(icon_path("warning"))
@@ -1121,6 +1122,7 @@ def __init__(
font.setPointSize(font.pointSize() - 1)
self.legend_label.setFont(font)
self.legend_label.setVisible(False)
+ self.legend_label.setTextFormat(Qt.TextFormat.RichText)
self.text_char_format = QTextCharFormat()
self.text_char_format.setBackground(QBrush(self.color))
self.text_char_format.setToolTip(tooltip)
### electrum/gui/qt/update_checker.py
@@ -35,9 +35,11 @@ def __init__(self, *, latest_version=None):
self.content.setContentsMargins(*[10]*4)
self.heading_label = QLabel()
+ self.heading_label.setTextFormat(Qt.TextFormat.RichText)
self.content.addWidget(self.heading_label)
self.detail_label = QLabel()
+ self.detail_label.setTextFormat(Qt.TextFormat.RichText)
self.detail_label.setTextInteractionFlags(Qt.TextInteractionFlag.LinksAccessibleByMouse)
self.detail_label.setOpenExternalLinks(True)
self.content.addWidget(self.detail_label)
### electrum/gui/qt/util.py
@@ -120,6 +120,7 @@ def __init__(self, text='', parent=None):
WWLabel.__init__(self, text, parent)
self.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction)
self.setOpenExternalLinks(True)
+ self.setTextFormat(Qt.TextFormat.RichText)
class AmountLabel(QLabel):
### electrum/gui/qt/wizard/wizard.py
@@ -39,6 +39,7 @@ def __init__(self, config: 'SimpleConfig', app: 'QElectrumApplication', *, start
self.setMinimumSize(600, 400)
self.title = QLabel()
+ self.title.setTextFormat(Qt.TextFormat.RichText)
self.window_title = ''
self.finish_label = _('Finish')
### electrum/plugins/coldcard/qt.py
@@ -193,6 +193,7 @@ def connect_and_doit():
for row_num, (member_name, label) in enumerate(rows):
# XXX we know xfp already, even if not connected
widget = QLabel('<tt>000000000000')
+ widget.setTextFormat(Qt.TextFormat.RichText)
widget.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse | Qt.TextInteractionFlag.TextSelectableByKeyboard)
grid.addWidget(QLabel(label), y, 0, 1, 1, Qt.AlignmentFlag.AlignRight)
### electrum/plugins/revealer/qt.py
@@ -364,7 +364,9 @@ def cypherseed_dialog(self, window):
self.custom_secret_maximum_characters_warning_label = QLabel("<font color='red'>"
+ _("This version supports a maximum of {} characters.").format(self.MAX_PLAINTEXT_LEN)
+"</font>")
+ self.custom_secret_maximum_characters_warning_label.setTextFormat(Qt.TextFormat.RichText)
one_time_pad_warning_label = QLabel("<b>" + _("Warning ") + "</b>: " + _("each Revealer is a one-time-pad, use it for a single secret."))
+ one_time_pad_warning_label.setTextFormat(Qt.TextFormat.RichText)
# Allow users to select text in the labels.
ready_to_encrypt_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
@@ -853,9 +855,10 @@ def calibration_dialog(self, window):
d.setMinimumSize(100, 200)
vbox = QVBoxLayout(d)
- vbox.addWidget(QLabel(''.join(["<br/>", _("If you have an old printer, or want optimal precision"),"<br/>",
- _("print the calibration pdf and follow the instructions "), "<br/>","<br/>",
- ])))
+ vbox.addWidget(QLabel("".join([
+ _("If you have an old printer, or want optimal precision"), "\n",
+ _("print the calibration pdf and follow the instructions "), "\n\n",
+ ])))
self.calibration_h = self.config.get('calibration_h')
self.calibration_v = self.config.get('calibration_v')
cprint = QPushButton(_("Open calibration pdf"))
### electrum/plugins/trustedcoin/qt.py
@@ -187,6 +187,7 @@ def show_settings_dialog(self, window, success):
+ _("For more information, visit") + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>"
label = QLabel(msg)
label.setOpenExternalLinks(True)
+ label.setTextFormat(Qt.TextFormat.RichText)
hbox.addStretch(10)
hbox.addWidget(logo)
@@ -197,7 +198,9 @@ def show_settings_dialog(self, window, success):
vbox.addLayout(hbox)
vbox.addStretch(10)
- msg = _('TrustedCoin charges a small fee to co-sign transactions. The fee depends on how many prepaid transactions you buy. An extra output is added to your transaction every time you run out of prepaid transactions.') + '<br/>'
+ msg = _('TrustedCoin charges a small fee to co-sign transactions. '
+ 'The fee depends on how many prepaid transactions you buy. '
+ 'An extra output is added to your transaction every time you run out of prepaid transactions.')
label = QLabel(msg)
label.setWordWrap(True)
vbox.addWidget(label)Why this scored 37/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.