What changed, and why it matters
This commit adds user-configurable reminders to remove the microSD card from a SeedSigner hardware wallet. Previously the reminder stayed on screen essentially forever; now users can choose a 5-second toast, no reminder, or a blocking warning that requires removing the card. It is a usability and safety enhancement, not a fix for an exploitable vulnerability.
No immediate action required. Review the new setting's default and help text for clarity; ensure the `MicroSD.get_instance().is_inserted` check is reliable across hardware revisions.
Security signals we found
Adds explicit user-facing safety control for removable storage
Reduces default duration of persistent microSD removal toast from ~indefinite to 5 seconds
Blocking option enforces physical removal before UI proceeds
Evidence from the diff
The change introduces a new setting SETTING__MICROSD_TOAST_TIMER with three options: disabled, 5-second toast (default), and ‘Until SD removed’ (blocking fullscreen warning). RemoveSDCardToastManagerThread default duration is changed from 1e6 (effectively forever) to 5 seconds. When the blocking option is selected, Controller.start redirects to a new RemoveMicroSDWarningView that loops back to itself if the user presses Continue while the microSD is still inserted, otherwise proceeds to MainMenuView.
Changed components
src/seedsigner/controller.pysrc/seedsigner/gui/toast.pysrc/seedsigner/models/settings_definition.pysrc/seedsigner/views/view.pyInspect captured patch +50 / −4
diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py
index 5141723..ea7c9e8 100644
--- a/src/seedsigner/controller.py
+++ b/src/seedsigner/controller.py
@@ -11,6 +11,7 @@ from seedsigner.models.psbt_parser import PSBTParser
from seedsigner.models.seed import Seed
from seedsigner.models.seed_storage import SeedStorage
from seedsigner.models.settings import Settings
+from seedsigner.models.settings import SettingsConstants
from seedsigner.models.singleton import Singleton
from seedsigner.models.threads import BaseThread
from seedsigner.views.screensaver import ScreensaverScreen
@@ -252,7 +253,7 @@ class Controller(Singleton):
* initial_destination: The first View to run. If None, the MainMenuView is
used. Only used by the test suite.
"""
- from seedsigner.views import MainMenuView, BackStackView
+ from seedsigner.views import MainMenuView, BackStackView, RemoveMicroSDWarningView
from seedsigner.views.screensaver import OpeningSplashView
from seedsigner.gui.toast import RemoveSDCardToastManagerThread
@@ -290,7 +291,10 @@ class Controller(Singleton):
next_destination = Destination(MainMenuView)
# Set up our one-time toast notification tip to remove the SD card
- self.activate_toast(RemoveSDCardToastManagerThread())
+ if self.settings.get_value(SettingsConstants.SETTING__MICROSD_TOAST_TIMER) == SettingsConstants.MICROSD_TOAST_TIMER_FIVE_SECONDS:
+ self.activate_toast(RemoveSDCardToastManagerThread())
+ elif self.settings.get_value(SettingsConstants.SETTING__MICROSD_TOAST_TIMER) == SettingsConstants.MICROSD_TOAST_TIMER_FOREVER:
+ next_destination = Destination(RemoveMicroSDWarningView)
while True:
# Destination(None) is a special case; render the Home screen
diff --git a/src/seedsigner/gui/toast.py b/src/seedsigner/gui/toast.py
index f75d6a6..2f7a406 100644
--- a/src/seedsigner/gui/toast.py
+++ b/src/seedsigner/gui/toast.py
@@ -201,11 +201,11 @@ class BaseToastOverlayManagerThread(BaseThread):
class RemoveSDCardToastManagerThread(BaseToastOverlayManagerThread):
- def __init__(self, activation_delay: int = 3, duration: int = 1e6):
+ def __init__(self, activation_delay: int = 3, duration: int = 5):
"""
* activation_delay: configurable so the screenshot generator can get the
toast to immediately render.
- * duration: default value is essentially forever. Overrideable for the
+ * duration: default value is 5 seconds. Overrideable for the
screenshot generator.
"""
super().__init__(
diff --git a/src/seedsigner/models/settings_definition.py b/src/seedsigner/models/settings_definition.py
index a036956..2a6a1d7 100644
--- a/src/seedsigner/models/settings_definition.py
+++ b/src/seedsigner/models/settings_definition.py
@@ -293,6 +293,15 @@ class SettingsConstants:
(CUSTOM_DERIVATION, _mft("Custom Derivation")),
]
+ MICROSD_TOAST_TIMER_DISABLED = "D"
+ MICROSD_TOAST_TIMER_FIVE_SECONDS = "E"
+ MICROSD_TOAST_TIMER_FOREVER = "inf"
+ ALL_MICROSD_TOAST_TIMERS = [
+ (MICROSD_TOAST_TIMER_DISABLED, "Disabled"),
+ (MICROSD_TOAST_TIMER_FIVE_SECONDS, "5 seconds"),
+ (MICROSD_TOAST_TIMER_FOREVER, "Until SD removed")
+ ]
+
WORDLIST_LANGUAGE__ENGLISH = "en"
WORDLIST_LANGUAGE__CHINESE_SIMPLIFIED = "zh_Hans_CN"
WORDLIST_LANGUAGE__CHINESE_TRADITIONAL = "zh_Hant_TW"
@@ -339,6 +348,7 @@ class SettingsConstants:
SETTING__DIRE_WARNINGS = "dire_warnings"
SETTING__QR_BRIGHTNESS_TIPS = "qr_brightness_tips"
SETTING__PARTNER_LOGOS = "partner_logos"
+ SETTING__MICROSD_TOAST_TIMER = "microsd_toast_timer"
SETTING__DEBUG = "debug"
@@ -665,6 +675,14 @@ class SettingsDefinition:
help_text=_mft("Native Segwit only"),
visibility=SettingsConstants.VISIBILITY__ADVANCED,
default_value=SettingsConstants.OPTION__DISABLED),
+
+ SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
+ attr_name=SettingsConstants.SETTING__MICROSD_TOAST_TIMER,
+ display_name="MicroSD toast timer",
+ type=SettingsConstants.TYPE__SELECT_1,
+ visibility=SettingsConstants.VISIBILITY__ADVANCED,
+ selection_options=SettingsConstants.ALL_MICROSD_TOAST_TIMERS,
+ default_value=SettingsConstants.MICROSD_TOAST_TIMER_FIVE_SECONDS),
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
attr_name=SettingsConstants.SETTING__MESSAGE_SIGNING,
diff --git a/src/seedsigner/views/view.py b/src/seedsigner/views/view.py
index 2c0e3d1..d7e0c26 100644
--- a/src/seedsigner/views/view.py
+++ b/src/seedsigner/views/view.py
@@ -407,3 +407,27 @@ class OptionDisabledView(View):
return Destination(SettingsEntryUpdateSelectionView, view_args=dict(attr_name=self.settings_attr), clear_history=True)
else:
return Destination(MainMenuView, clear_history=True)
+
+
+
+class RemoveMicroSDWarningView(View):
+ CONTINUE = ButtonOption("Continue")
+ DISMISS = ButtonOption("Dismiss")
+
+ def run(self):
+ button_data = [self.CONTINUE, self.DISMISS]
+ selected_menu_num = self.run_screen(
+ WarningScreen,
+ title=_("Action Required"),
+ status_icon_name=SeedSignerIconConstants.MICROSD,
+ status_headline=None,
+ text=_("You must remove the\nMicroSD card to continue."),
+ show_back_button=False,
+ button_data=button_data,
+ )
+
+ from seedsigner.hardware.microsd import MicroSD
+ if button_data[selected_menu_num] == self.CONTINUE and MicroSD.get_instance().is_inserted:
+ return Destination(RemoveMicroSDWarningView, clear_history=True)
+ else:
+ return Destination(MainMenuView, clear_history=True)
Why this scored 18/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.