fix: do not report "Failed to load" when the user backs out
What changed, and why it matters
This commit is a user-experience fix: it stops the Krux bitcoin hardware-wallet firmware from showing a scary 'Failed to load' error when the user simply presses Back or leaves the camera/SD-card picker. The change also moves SD-card error reporting into one shared helper so real failures are still reported. There is no security vulnerability being fixed here.
No security action required. Treat as a normal UX/bug-fix commit. Reviewers may optionally verify that genuine SD-card read failures still flash 'Failed to load' via the centralized Utils.load_file path.
Security signals we found
No memory-safety, cryptographic, or authorization changes
Error-message UX change only
No new attack surface introduced
No input validation or parsing logic modified
Evidence from the diff
The patch refactors cancellation handling across multiple UI flows. Previously, several callers interpreted a None/empty result from QR capture or SD file selection as a load failure and flashed t(‘Failed to load’). The commit makes those callers return MENU_CONTINUE/None silently when the user cancels, and shifts the responsibility for reporting genuine OSError or missing-SD failures into Utils.load_file(). Return signatures are simplified (e.g., load_psbt now returns None on cancel instead of (None,None,’‘)). No cryptographic, parsing, or trust-boundary logic is changed.
Changed components
src/krux/pages/datum_tool.pysrc/krux/pages/encryption_ui.pysrc/krux/pages/home_pages/addresses.pysrc/krux/pages/home_pages/home.pysrc/krux/pages/home_pages/sign_message_ui.pysrc/krux/pages/home_pages/wallet_descriptor.pysrc/krux/pages/mnemonic_loader.pysrc/krux/pages/utils.pysrc/krux/pages/wallet_settings.pyInspect captured patch +54 / −50
### src/krux/pages/datum_tool.py
@@ -366,15 +366,8 @@ def read_file(self):
"""Handler for the 'Read File' menu item"""
from .utils import Utils
- if not self.has_sd_card():
- self.flash_error(t("SD card not detected."))
- return MENU_CONTINUE
-
utils = Utils(self.ctx)
- try:
- filename, contents = utils.load_file(prompt=False)
- except OSError:
- pass
+ filename, contents = utils.load_file(prompt=False)
if not contents:
return MENU_CONTINUE
### src/krux/pages/encryption_ui.py
@@ -503,7 +503,7 @@ def encryption_key(self, creating=False):
while True:
if key in (None, "", b"", ESC_KEY, MENU_CONTINUE):
- self.flash_error(t("Failed to load"))
+ # user cancelled the keypad or left the QR scanner
return None
self.ctx.display.clear()
### src/krux/pages/home_pages/addresses.py
@@ -292,7 +292,9 @@ def scan_address(self, addr_type=0):
qr_capture = QRCodeCapture(self.ctx)
data, qr_format = qr_capture.qr_capture_loop()
- if data is None or qr_format != FORMAT_NONE:
+ if data is None: # user left the QR scanner
+ return MENU_CONTINUE
+ if qr_format != FORMAT_NONE:
self.flash_error(t("Failed to load"))
return MENU_CONTINUE
### src/krux/pages/home_pages/home.py
@@ -222,13 +222,15 @@ def load_psbt(self):
load_method = self.load_method()
if load_method > LOAD_FROM_SD:
- return (None, None, "")
+ return None # user chose Back
if load_method == LOAD_FROM_CAMERA:
from ..qr_capture import QRCodeCapture
qr_capture = QRCodeCapture(self.ctx)
data, qr_format = qr_capture.qr_capture_loop()
+ if data is None:
+ return None # user left the QR scanner
return (data, qr_format, "")
# If load_method == LOAD_FROM_SD
@@ -241,6 +243,8 @@ def load_psbt(self):
prompt=False,
only_get_filename=True,
)
+ if not psbt_filename:
+ return None # user backed out of the SD file browser
return (None, FORMAT_NONE, psbt_filename)
def _sign_menu(self, signer, psbt_filename, outputs):
@@ -487,12 +491,10 @@ def sign_psbt(self):
return MENU_CONTINUE
# Load a PSBT
- data, qr_format, psbt_filename = self.load_psbt()
-
- if data is None and psbt_filename == "":
- # Both the camera and the file on SD card failed!
- self.flash_error(t("Failed to load"))
+ loaded = self.load_psbt()
+ if loaded is None: # user chose Back on the load menu
return MENU_CONTINUE
+ data, qr_format, psbt_filename = loaded
# DISABLED to avoid false "Decrypt?" on normal PSBTs as KEF
# try:
### src/krux/pages/home_pages/sign_message_ui.py
@@ -62,11 +62,15 @@ def _load_message(self):
qr_capture = QRCodeCapture(self.ctx)
data, qr_format = qr_capture.qr_capture_loop()
+ if data is None:
+ return None # user left the QR scanner
return (data, qr_format, "")
if load_method == LOAD_FROM_SD:
message_filename, data = self.load_file(prompt=False)
+ if not message_filename:
+ return None # user backed out of the SD file browser
return (data, FORMAT_NONE, message_filename)
- return (None, None, "")
+ return None # user chose Back
def _is_valid_derivation_path(self, derivation_path):
"""Strictly checks if the derivation path is valid according to BIP32"""
@@ -368,11 +372,10 @@ def _export_to_sd(self, sig, pubkey, message_filename, message="", address=""):
def sign_message(self):
"""Sign message user interface"""
- data, qr_format, message_filename = self._load_message()
-
- if data is None:
- self.flash_error(t("Failed to load"))
+ loaded = self._load_message()
+ if loaded is None: # user chose Back on the load menu
return MENU_CONTINUE
+ data, qr_format, message_filename = loaded
if message_filename:
signature_data = self._sign_at_address_from_sd(data)
### src/krux/pages/home_pages/wallet_descriptor.py
@@ -133,32 +133,30 @@ def _load_wallet_data(self):
qr_capture = QRCodeCapture(self.ctx)
wallet_data, qr_format = qr_capture.qr_capture_loop()
+ if wallet_data is None:
+ return None # user left the QR scanner
elif load_method == LOAD_FROM_SD:
# Try to read the wallet output descriptor from a file on the SD card
qr_format = FORMAT_NONE
- try:
- from ..utils import Utils
+ from ..utils import Utils
- utils = Utils(self.ctx)
- _, wallet_data = utils.load_file(
- (
- DESCRIPTOR_FILE_EXTENSION,
- JSON_FILE_EXTENSION,
- ),
- prompt=False,
- )
- persisted = True
- except OSError:
- pass
+ utils = Utils(self.ctx)
+ filename, wallet_data = utils.load_file(
+ (
+ DESCRIPTOR_FILE_EXTENSION,
+ JSON_FILE_EXTENSION,
+ ),
+ prompt=False,
+ )
+ if not filename:
+ # Cancelled, or load_file already reported the failure
+ return None
+ persisted = True
else: # Cancel
- return None, None, False
+ return None
self.ctx.display.clear()
self.ctx.display.draw_centered_text(t("Processing…"))
- if wallet_data is None:
- # Camera or SD card loading failed!
- self.flash_error(t("Failed to load"))
- return None, None, False
# Decrypt if needed
from ..encryption_ui import decrypt_kef
@@ -171,10 +169,10 @@ def _load_wallet_data(self):
wallet_data = wallet_data.decode()
except:
self.flash_error(t("Failed to load"))
- return None, None, False
+ return None
except KeyError:
self.flash_error(t("Failed to decrypt"))
- return None, None, False
+ return None
except ValueError:
# ValueError=not KEF or declined to decrypt
pass
@@ -322,9 +320,10 @@ def _attempt_wallet_load(self, wallet_data, qr_format, persisted):
def _load_wallet(self):
"""Load a wallet output descriptor from the camera or SD card"""
# Load wallet data
- wallet_data, qr_format, persisted = self._load_wallet_data()
- if wallet_data is None:
+ loaded = self._load_wallet_data()
+ if loaded is None: # user chose Back, or the load already reported itself
return MENU_CONTINUE
+ wallet_data, qr_format, persisted = loaded
# Attempt to load the wallet with exception handling
wallet, wallet_load_exception = self._attempt_wallet_load(
### src/krux/pages/mnemonic_loader.py
@@ -299,8 +299,7 @@ def load_key_from_tiny_seed_image(self, grid_type="Tinyseed"):
tiny_scanner = TinyScanner(self.ctx, grid_type)
words = tiny_scanner.scanner(len_mnemonic == 24)
del tiny_scanner
- if words is None:
- self.flash_error(t("Failed to load"))
+ if words is None: # user left the scanner
return MENU_CONTINUE
return self._load_key_from_words(words)
@@ -311,8 +310,7 @@ def load_key_from_qr_code(self):
qr_capture = QRCodeCapture(self.ctx)
data, qr_format = qr_capture.qr_capture_loop()
- if data is None:
- self.flash_error(t("Failed to load"))
+ if data is None: # user left the QR scanner
return MENU_CONTINUE
try:
### src/krux/pages/utils.py
@@ -56,7 +56,13 @@ def load_file(self, file_ext="", prompt=True, only_get_filename=False):
"""Load a file from SD card"""
from ..sd_card import SDHandler
- if self.has_sd_card():
+ # Callers read an empty filename as "user cancelled", so every failure
+ # has to report itself here or it would be swallowed silently.
+ if not self.has_sd_card():
+ self.flash_error(t("SD card not detected."))
+ return "", None
+
+ try:
with SDHandler() as sd:
self.ctx.display.clear()
if not prompt or self.prompt(
@@ -75,6 +81,8 @@ def load_file(self, file_ext="", prompt=True, only_get_filename=False):
if only_get_filename:
return filename, None
return filename, sd.read_binary(filename)
+ except OSError:
+ self.flash_error(t("Failed to load"))
return "", None
@staticmethod
### src/krux/pages/wallet_settings.py
@@ -143,8 +143,7 @@ def _load_qr_passphrase(self):
qr_capture = QRCodeCapture(self.ctx)
data, _ = qr_capture.qr_capture_loop()
- if data is None:
- self.flash_error(t("Failed to load"))
+ if data is None: # user left the QR scanner
return MENU_CONTINUE
try:Why this scored 21/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.