From fcef5345000dee6fff6d111e2502692c09aaeb6e Mon Sep 17 00:00:00 2001 From: Charles-Edouard de la Vergne Date: Thu, 18 Jun 2026 17:02:32 +0200 Subject: [PATCH 01/10] wui: save backup with a dedicated step to keep the file dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reading the card consumes the click's transient user activation, so showSaveFilePicker cannot be called from the continuation of the device exchange (it throws "Must be handling a user gesture"); opening it before the approval instead would create the chosen file on disk and leave it empty if the device is rejected. Use a two-step flow: back up first, then surface a "Save…" button that opens the native save dialog from its own click. This keeps the file-name dialog without the SecurityError and without creating an empty file on rejection; falls back to an anchor download where the File System Access API is missing. --- CHANGELOG.md | 2 - clients/wui/src/App.jsx | 101 ++++++++++++++++++++++++++-------------- 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d245750..844c576 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fix -- WUI: saving a backup no longer fails with a `SecurityError` — the save dialog now opens on click, - before the on-device approval. - Demo passwords from the developer `POPULATE` build are seeded only once (on first storage initialization). ### Change diff --git a/clients/wui/src/App.jsx b/clients/wui/src/App.jsx index d7f9007..47bce29 100644 --- a/clients/wui/src/App.jsx +++ b/clients/wui/src/App.jsx @@ -26,50 +26,48 @@ import packageJson from "../package.json"; const passwords = new PasswordsManager(); listen((log) => console.log(log)); -// showSaveFilePicker() requires transient user activation (a recent gesture), -// so it must be called synchronously from the click handler — before the -// device exchange, which takes too long and would otherwise let the activation -// expire ("Must be handling a user gesture"). This picks the destination up -// front and returns a target the caller writes to once the data is ready. -// Falls back to a Blob/anchor download when the File System Access API is -// unavailable or refuses (returns { cancelled } if the user dismisses it). -async function pickSaveTarget(suggestedName) { +// Save the backup, preferring the native "Save As" dialog so the user can +// choose the file name/location. showSaveFilePicker requires transient user +// activation, so this MUST be called from its own click handler — not from the +// async continuation of the device exchange (the original activation would have +// expired -> "Must be handling a user gesture"). Hence the two-step flow: +// backup first, then a separate "Save" click. Returns false if the user +// cancels the dialog (so the caller can keep the data for a retry). Falls back +// to a plain anchor download where the File System Access API is unavailable. +async function saveBackupFile(payload, filename) { + const text = JSON.stringify(payload, null, 4); + if (window.showSaveFilePicker) { + let handle = null; try { - const handle = await window.showSaveFilePicker({ - suggestedName, + handle = await window.showSaveFilePicker({ + suggestedName: filename, types: [ { description: "Passwords backup", accept: { "application/json": [".json"] } }, ], }); - return { handle, suggestedName }; } catch (error) { - if (error.name === "AbortError") return { cancelled: true }; - // SecurityError / NotAllowedError / unsupported: fall back to anchor. + if (error.name === "AbortError") return false; // user dismissed the dialog + // Other errors (unsupported / SecurityError): fall through to the anchor. + } + if (handle) { + const writable = await handle.createWritable(); + await writable.write(text); + await writable.close(); + return true; } - } - return { suggestedName }; -} - -async function writeJSON(target, payload) { - const text = JSON.stringify(payload, null, 4); - - if (target.handle) { - const writable = await target.handle.createWritable(); - await writable.write(text); - await writable.close(); - return; } const blob = new Blob([text], { type: "application/json;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; - a.download = target.suggestedName; + a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); + return true; } export default function App() { @@ -82,6 +80,10 @@ export default function App() { const [version, setVersion] = useState(mock ? "1.0.0" : null); const [storageSize, setStorageSize] = useState(mock ? 4096 : null); const [notice, setNotice] = useState(null); + // Backup data read from the card, waiting for the user to pick a file. The + // save dialog needs its own click (transient activation), so it can't be + // chained onto the device exchange. + const [pendingBackup, setPendingBackup] = useState(null); const fileInput = useRef(null); @@ -130,20 +132,20 @@ export default function App() { } async function onBackup() { - // Pick the destination first, while the click's user activation is still - // valid (the device exchange below is too slow for showSaveFilePicker). - const target = await pickSaveTarget("passwords_backup.json"); - if (target.cancelled) { - setNotice({ appearance: "info", title: "Backup cancelled" }); - return; - } - setBusy(true); + setPendingBackup(null); setNotice({ appearance: "info", title: 'Approve "Transfer metadatas?" on your device' }); try { + // Read the card first. The file dialog can't be opened here (the device + // approval consumed the click's activation), so hold the data and let the + // user trigger the save with a fresh click. const payload = await passwords.dump_metadatas(); - await writeJSON(target, payload); - setNotice({ appearance: "success", title: "Backup saved" }); + setPendingBackup(payload); + setNotice({ + appearance: "success", + title: "Backup ready", + description: "Click Save to choose where to store the file.", + }); } catch (error) { setNotice({ appearance: "error", title: "Backup failed", description: String(error) }); } finally { @@ -151,6 +153,19 @@ export default function App() { } } + async function onSaveBackup() { + try { + const saved = await saveBackupFile(pendingBackup, "passwords_backup.json"); + if (saved) { + setPendingBackup(null); + setNotice({ appearance: "success", title: "Backup saved" }); + } + // If cancelled, keep pendingBackup so the user can try saving again. + } catch (error) { + setNotice({ appearance: "error", title: "Save failed", description: String(error) }); + } + } + function onPickRestoreFile(event) { const file = event.target.files[0]; event.target.value = ""; @@ -196,6 +211,20 @@ export default function App() { /> )} + {/* Backup ready: the save dialog needs its own click. */} + {pendingBackup && ( + + Save… + + } + /> + )} + {/* Device card */}
From e08c1a655c01a1c68e1f9ee8bc79c475eb7b0222 Mon Sep 17 00:00:00 2001 From: Charles-Edouard de la Vergne Date: Thu, 18 Jun 2026 17:21:20 +0200 Subject: [PATCH 02/10] wui: drop the storage-size row from the device card The "Storage 4096 bytes" line showed the total metadata capacity read from GET_APP_CONFIG. It is a constant, so it adds noise to the device card.The controller still reads storage_size internally for buffer sizing;only the UI row and its React state are removed. --- clients/wui/src/App.jsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/clients/wui/src/App.jsx b/clients/wui/src/App.jsx index 47bce29..4546816 100644 --- a/clients/wui/src/App.jsx +++ b/clients/wui/src/App.jsx @@ -78,7 +78,6 @@ export default function App() { const [busy, setBusy] = useState(false); const [connected, setConnected] = useState(mock); const [version, setVersion] = useState(mock ? "1.0.0" : null); - const [storageSize, setStorageSize] = useState(mock ? 4096 : null); const [notice, setNotice] = useState(null); // Backup data read from the card, waiting for the user to pick a file. The // save dialog needs its own click (transient activation), so it can't be @@ -92,7 +91,6 @@ export default function App() { setConnected(false); setBusy(false); setVersion(null); - setStorageSize(null); setNotice({ appearance: "warning", title: "Device disconnected", @@ -109,14 +107,12 @@ export default function App() { try { await passwords.connect(); setVersion(passwords.version); - setStorageSize(passwords.storage_size); setConnected(true); setNotice({ appearance: "success", title: "Device connected" }); } catch (error) { await passwords.disconnect(); setConnected(false); setVersion(null); - setStorageSize(null); setNotice({ appearance: "error", title: "Connection failed", description: String(error) }); } finally { setBusy(false); @@ -127,7 +123,6 @@ export default function App() { await passwords.disconnect(); setConnected(false); setVersion(null); - setStorageSize(null); setNotice({ appearance: "info", title: "Disconnected" }); } @@ -251,12 +246,6 @@ export default function App() { Version
-
- Storage - - {storageSize ? `${storageSize} bytes` : "—"} - -
)} From 447697315a1a7e46226a2e4450b8d7f135845145 Mon Sep 17 00:00:00 2001 From: Charles-Edouard de la Vergne Date: Thu, 18 Jun 2026 17:22:56 +0200 Subject: [PATCH 03/10] Fix metadata offset truncation on restore override_metadatas() took the destination offset as a uint8_t, but load_metadatas() passes app_state.bytes_transferred (size_t, up to MAX_METADATAS = 4096).Small backups happened to work because only the first chunk carries data;larger lists (> ~255 bytes of metadata) were corrupted. --- src/metadata.c | 2 +- src/metadata.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/metadata.c b/src/metadata.c index ff7b804..b9c51fe 100644 --- a/src/metadata.c +++ b/src/metadata.c @@ -28,7 +28,7 @@ error_type_t write_metadata(uint8_t *data, uint8_t dataSize) { return OK; } -void override_metadatas(uint8_t offset, void *ptr, size_t size) { +void override_metadatas(size_t offset, void *ptr, size_t size) { nvm_write((void *) &N_storage.metadatas[offset], ptr, size); } diff --git a/src/metadata.h b/src/metadata.h index a128e8c..9e8b6d7 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -24,7 +24,7 @@ error_type_t write_metadata(uint8_t *data, uint8_t dataSize); * Write a given amount of data on metadatas, at the given offset * Used to load metadata from APDUs */ -void override_metadatas(uint8_t offset, void *ptr, size_t size); +void override_metadatas(size_t offset, void *ptr, size_t size); void reset_metadatas(void); error_type_t erase_metadata(uint32_t offset); From 9eab2e10aa8f08cc447223694354b17540ba90d6 Mon Sep 17 00:00:00 2001 From: Charles-Edouard de la Vergne Date: Thu, 18 Jun 2026 17:28:25 +0200 Subject: [PATCH 04/10] Add unit tests for metadata offset and capacity limits Cover the boundary behaviour of the metadata store: - override_metadatas at offset 300 lands at 300, not at 300 % 256 == 44 (regression guard for the uint8_t-offset truncation fix), plus a write at the very end of the buffer. - write_metadata refuses new entries once full (ERR_NO_MORE_SPACE_AVAILABLE), so the 4096-byte buffer cannot overflow. --- tests/unit/test_password.c | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/unit/test_password.c b/tests/unit/test_password.c index cfff917..aaabd64 100644 --- a/tests/unit/test_password.c +++ b/tests/unit/test_password.c @@ -128,6 +128,48 @@ static void test_nickname_exists_truncation(void **state __attribute__((unused)) assert_true(nickname_exists(twenty_chars_same_prefix, MAX_METANAME)); } +static void test_override_metadatas_high_offset(void **state __attribute__((unused))) { + // Regression: override_metadatas() took the offset as a uint8_t, so offsets + // >= 256 were truncated mod 256. A restore is streamed in 255-byte chunks, + // so the second chunk onward landed at the wrong place and overwrote earlier + // data. Writing at offset 300 must land at 300, not at 300 % 256 == 44. + const uint8_t payload[] = {0xAA, 0xBB, 0xCC}; + override_metadatas(300, (void *) payload, sizeof(payload)); + + assert_memory_equal(&N_storage_real.metadatas[300], payload, sizeof(payload)); + // The location it would have hit when truncated must be untouched. + assert_int_equal(N_storage_real.metadatas[44], 0); + assert_int_equal(N_storage_real.metadatas[45], 0); + assert_int_equal(N_storage_real.metadatas[46], 0); +} + +static void test_override_metadatas_buffer_end(void **state __attribute__((unused))) { + // Writing the final bytes (offset + size == MAX_METADATAS) must be placed + // correctly and stay within the buffer. + const uint8_t payload[] = {0x11, 0x22, 0x33, 0x44}; + const size_t offset = MAX_METADATAS - sizeof(payload); + override_metadatas(offset, (void *) payload, sizeof(payload)); + + assert_memory_equal(&N_storage_real.metadatas[offset], payload, sizeof(payload)); +} + +static void test_write_metadata_enforces_capacity(void **state __attribute__((unused))) { + // The store must refuse new entries once full and never report success past + // the limit, so the 4096-byte buffer can never overflow. + uint8_t name[MAX_METANAME]; + memset(name, 'A', sizeof(name)); + + error_type_t err = OK; + int written = 0; + while ((err = write_metadata(name, sizeof(name))) == OK) { + // Guard against an unbounded loop if the limit were not enforced. + assert_true(++written < MAX_METADATAS); + } + + assert_int_equal(err, ERR_NO_MORE_SPACE_AVAILABLE); + assert_true(written > 0); +} + int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown(test_nickname_exists_empty_db, setup, NULL), @@ -136,6 +178,9 @@ int main(void) { cmocka_unit_test_setup_teardown(test_nickname_exists_length_mismatch, setup, NULL), cmocka_unit_test_setup_teardown(test_nickname_exists_case_sensitive, setup, NULL), cmocka_unit_test_setup_teardown(test_nickname_exists_truncation, setup, NULL), + cmocka_unit_test_setup_teardown(test_override_metadatas_high_offset, setup, NULL), + cmocka_unit_test_setup_teardown(test_override_metadatas_buffer_end, setup, NULL), + cmocka_unit_test_setup_teardown(test_write_metadata_enforces_capacity, setup, NULL), }; return cmocka_run_group_tests(tests, NULL, NULL); } From 0ea174e804389d5ffa837b06c6f4eaadffac49c7 Mon Sep 17 00:00:00 2001 From: Charles-Edouard de la Vergne Date: Thu, 18 Jun 2026 17:38:18 +0200 Subject: [PATCH 05/10] Compile the test APDU handler only under TESTING The RUN_TEST dispatch was already guarded by #ifdef TESTING, but tests.c was compiled unconditionally (APP_SOURCE_PATH globs all of src/) and dispatcher.c included tests.h unconditionally. Guard the test handler's definitions and the include with #ifdef TESTING so production builds (TESTING=0) contain no test code at all, instead of relying on linker dead-code elimination. The #include lines in tests.c stay outside the guard to avoid an empty translation unit. --- src/dispatcher.c | 2 ++ src/tests/tests.c | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/dispatcher.c b/src/dispatcher.c index ce40790..696f0db 100644 --- a/src/dispatcher.c +++ b/src/dispatcher.c @@ -23,7 +23,9 @@ #include "dispatcher.h" #include "error.h" #include "globals.h" +#ifdef TESTING #include "tests.h" +#endif #include "types.h" int dispatch() { diff --git a/src/tests/tests.c b/src/tests/tests.c index e2446fc..ca29ed0 100644 --- a/src/tests/tests.c +++ b/src/tests/tests.c @@ -4,6 +4,10 @@ #include "password_typing.h" #include "tests.h" +// Test-only APDU handler: compiled into nothing unless built with TESTING=1, +// matching the `#ifdef TESTING` guard on the dispatch site in dispatcher.c. +#ifdef TESTING + /* Takes a metadata as an input (charset + seed) and returns a 20 char password*/ int test_generate_password(const buf_t *input) { uint8_t enabledSets = input->bytes[0]; @@ -37,3 +41,5 @@ int test_dispatcher(uint8_t p1, __attribute__((unused)) uint8_t p2, const buf_t return io_send_sw(SWO_INVALID_INS + 1); } } + +#endif // TESTING From 35c4c200e1824cba06911b6236e2dff30fb22915 Mon Sep 17 00:00:00 2001 From: Charles-Edouard de la Vergne Date: Thu, 18 Jun 2026 18:29:44 +0200 Subject: [PATCH 06/10] Clarify the backup/restore confirmation wording Replace the device prompts "Transfer metadatas ?" / "Overwrite metadatas ?" with "Backup" / "Restore" + "password list", aligned with the WebUI buttons, and without the stray space before "?". Wallet screens keep the question mark; the Nano variant drops it. The WebUI notices, FAQ and README that quote these prompts are updated to match. --- clients/wui/README.md | 6 +++--- clients/wui/src/App.jsx | 4 ++-- clients/wui/src/components/Faq.jsx | 4 ++-- src/apdu_handlers/dump_metadatas.c | 6 +++++- src/apdu_handlers/load_metadatas.c | 6 +++++- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/clients/wui/README.md b/clients/wui/README.md index 014251f..b90c405 100644 --- a/clients/wui/README.md +++ b/clients/wui/README.md @@ -37,9 +37,9 @@ For more information on the device application itself, see the - Click **Connect** — on success the Backup / Restore actions appear. (Connection trouble? See [Fix connection issues](https://support.ledger.com/article/115005165269-zd).) -- **Backup** prompts "Transfer metadatas?" on the device, then saves the file. -- **Restore** asks for a previous backup file, then prompts "Overwrite - metadatas?" on the device. +- **Backup** prompts "Backup password list?" on the device, then saves the file. +- **Restore** asks for a previous backup file, then prompts "Restore password + list?" on the device. ## Develop diff --git a/clients/wui/src/App.jsx b/clients/wui/src/App.jsx index 4546816..0e8fab2 100644 --- a/clients/wui/src/App.jsx +++ b/clients/wui/src/App.jsx @@ -129,7 +129,7 @@ export default function App() { async function onBackup() { setBusy(true); setPendingBackup(null); - setNotice({ appearance: "info", title: 'Approve "Transfer metadatas?" on your device' }); + setNotice({ appearance: "info", title: 'Approve "Backup password list?" on your device' }); try { // Read the card first. The file dialog can't be opened here (the device // approval consumed the click's activation), so hold the data and let the @@ -168,7 +168,7 @@ export default function App() { const reader = new FileReader(); reader.onload = async () => { setBusy(true); - setNotice({ appearance: "info", title: 'Approve "Overwrite metadatas?" on your device' }); + setNotice({ appearance: "info", title: 'Approve "Restore password list?" on your device' }); try { await passwords.load_metadatas(reader.result); setNotice({ appearance: "success", title: "Restore complete" }); diff --git a/clients/wui/src/components/Faq.jsx b/clients/wui/src/components/Faq.jsx index fd49f1d..555483b 100644 --- a/clients/wui/src/components/Faq.jsx +++ b/clients/wui/src/components/Faq.jsx @@ -22,8 +22,8 @@ const ITEMS = [ a: "* Connect your Ledger device to your computer and open the `Passwords app`.\n" + "* Click on the big `Connect` button — on success the `Backup` and `Restore` buttons appear. If you have trouble with this step, have a look [here](https://support.ledger.com/article/115005165269-zd).\n" + - "* `Backup` will prompt a screen requesting your approval on your device (\"Transfer metadatas?\"), then save a backup file. This backup is not confidential, so you can for instance e-mail it to yourself to never lose it.\n" + - "* `Restore` will prompt a file input dialog where you should indicate a previous backup file. A prompt (\"Overwrite metadatas?\") will then request your approval on your device.", + "* `Backup` will prompt a screen requesting your approval on your device (\"Backup password list?\"), then save a backup file. This backup is not confidential, so you can for instance e-mail it to yourself to never lose it.\n" + + "* `Restore` will prompt a file input dialog where you should indicate a previous backup file. A prompt (\"Restore password list?\") will then request your approval on your device.", }, { q: "Which web browsers and operating systems are supported?", diff --git a/src/apdu_handlers/dump_metadatas.c b/src/apdu_handlers/dump_metadatas.c index 26cc60f..1fb1c16 100644 --- a/src/apdu_handlers/dump_metadatas.c +++ b/src/apdu_handlers/dump_metadatas.c @@ -7,7 +7,11 @@ int dump_metadatas() { if (app_state.user_approval == false) { app_state.bytes_transferred = 0; - message_pair_t msg = {"Transfer", "metadatas ?"}; +#ifdef SCREEN_SIZE_WALLET + message_pair_t msg = {"Backup", "password list?"}; +#else + message_pair_t msg = {"Backup", "password list"}; +#endif ui_request_user_approval(&msg); return 0; } diff --git a/src/apdu_handlers/load_metadatas.c b/src/apdu_handlers/load_metadatas.c index 2c6dc25..2ba37dd 100644 --- a/src/apdu_handlers/load_metadatas.c +++ b/src/apdu_handlers/load_metadatas.c @@ -12,7 +12,11 @@ int load_metadatas(uint8_t p1, uint8_t p2, const buf_t *input) { } if (app_state.user_approval == false) { app_state.bytes_transferred = 0; - message_pair_t msg = {"Overwrite", "metadatas ?"}; +#ifdef SCREEN_SIZE_WALLET + message_pair_t msg = {"Restore", "password list?"}; +#else + message_pair_t msg = {"Restore", "password list"}; +#endif ui_request_user_approval(&msg); return 0; } From 97632febdbad727addc2ed60417e3864b67e094d Mon Sep 17 00:00:00 2001 From: Charles-Edouard de la Vergne Date: Thu, 18 Jun 2026 18:37:22 +0200 Subject: [PATCH 07/10] Add ragger tests for the backup/restore confirmation screens Also silence pylint's false no-member errors on the MetaScreen-generated CustomTouchScreen members (surfaced now that the file is touched). --- tests/functional/nano/navigator.py | 14 ++++++++++-- tests/functional/passwordsManager_cmd.py | 29 +++++++++++++++++------- tests/functional/test_render.py | 19 ++++++++++++++++ tests/functional/touch/navigator.py | 9 +++++++- 4 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 tests/functional/test_render.py diff --git a/tests/functional/nano/navigator.py b/tests/functional/nano/navigator.py index 10c49ec..bfcbc79 100644 --- a/tests/functional/nano/navigator.py +++ b/tests/functional/nano/navigator.py @@ -87,8 +87,11 @@ def __init__(self, backend: BackendInterface, device: Device, golden_run: bool = # paginates before the "Confirm" action page, so step forward until # that page is on screen and only then commit. CustomNavInsID.CONFIRM_YES: self._confirm_yes, - # APDU approval prompt + # Generic select/validate (settings switches, BARS_LIST entries). CustomNavInsID.BUTTON_APPROVE: self._both, + # APDU approval prompt (nbgl_useCaseConfirm): message page first, + # the "Approve" action sits on the next page (right then both). + CustomNavInsID.APDU_APPROVE: self._approve, # Keyboard (nbgl_useCaseKeyboard with MODE_NONE on Nano). CustomNavInsID.KEYBOARD_WRITE: self._write, CustomNavInsID.KEYBOARD_TO_CONFIRM: self._keyboard_confirm, @@ -106,13 +109,20 @@ def _left(self): def _both(self): self._backend.both_click() + def _approve(self): + # nbgl_useCaseConfirm shows the message first; the "Approve" action is + # reached with one right_click, then validated with both_click. Matches + # PasswordsManagerCommand.approve() for Nano. + self._right() + self._both() + def _navigate_until_text(self, text: str, max_steps: int = 12) -> None: """Press right_click until `text` appears on screen, then return.""" for _ in range(max_steps): try: self._backend.wait_for_text_on_screen(text, timeout=0.5) return - except Exception: + except TimeoutError: self._backend.right_click() raise RuntimeError(f"Could not find text '{text}' on screen after {max_steps} steps") diff --git a/tests/functional/passwordsManager_cmd.py b/tests/functional/passwordsManager_cmd.py index 005f75e..fbcfa40 100644 --- a/tests/functional/passwordsManager_cmd.py +++ b/tests/functional/passwordsManager_cmd.py @@ -37,8 +37,19 @@ def __init__(self, self.debug = debug self.approved: bool = False - def approve(self): - if self.device.touchable: + def approve(self, compare=None): + # compare = (snapshots_path, test_case_name) to snapshot-compare the + # on-device approval screen before approving. Works for both device + # families via the BUTTON_APPROVE navigation instruction. + if compare is not None: + snap_path, test_name = compare + self.navigation.navigate_and_compare( + snap_path, + test_name, + [CustomNavInsID.APDU_APPROVE], + screen_change_before_first_instruction=True, + screen_change_after_last_instruction=False) + elif self.device.touchable: self.navigation.navigate([CustomNavInsID.BUTTON_APPROVE]) else: self.transport.right_click() @@ -106,7 +117,7 @@ def generate_password(self, charsets: int, seed: str) -> str: return response.decode("ascii") - def dump_metadatas(self, size) -> bytes: + def dump_metadatas(self, size, compare=None) -> bytes: ins: InsType = InsType.INS_DUMP_METADATAS metadatas = b"" @@ -114,7 +125,7 @@ def dump_metadatas(self, size) -> bytes: while len(metadatas) < size: if not self.approved: with self.transport.exchange_async(cla=CLA, ins=ins): - self.approve() + self.approve(compare) response = self.transport.last_async_response self.approved = True else: @@ -131,14 +142,14 @@ def dump_metadatas(self, size) -> bytes: return metadatas[:size] - def load_metadatas_chunk(self, chunk, is_last): + def load_metadatas_chunk(self, chunk, is_last, compare=None): ins: InsType = InsType.INS_LOAD_METADATAS if not self.approved: with self.transport.exchange_async(cla=CLA, ins=ins, p1=0xFF if is_last else 0x00, data=chunk): - self.approve() + self.approve(compare) response = self.transport.last_async_response self.approved = True else: @@ -151,9 +162,11 @@ def load_metadatas_chunk(self, chunk, is_last): if not sw & 0x9000: raise DeviceException(error_code=sw, ins=ins) - def load_metadatas(self, metadatas): + def load_metadatas(self, metadatas, compare=None): chunks = [metadatas[i:i+255] for i in range(0, len(metadatas), 255)] self.approved = False for i, chunk in enumerate(chunks): - self.load_metadatas_chunk(chunk, i+1 == len(chunks)) + # Only the first chunk shows the approval screen. + self.load_metadatas_chunk(chunk, i+1 == len(chunks), + compare if i == 0 else None) diff --git a/tests/functional/test_render.py b/tests/functional/test_render.py new file mode 100644 index 0000000..c207e57 --- /dev/null +++ b/tests/functional/test_render.py @@ -0,0 +1,19 @@ +from pathlib import Path + +from passwordsManager_cmd import PasswordsManagerCommand + + +def test_backup_render(cmd: PasswordsManagerCommand, default_screenshot_path: Path): + # Triggering a backup makes the device show "Backup password list?". + # Capture that approval screen, then approve. + cmd.dump_metadatas(1, compare=(default_screenshot_path, "backup")) + cmd.reset_approval_state() + + +def test_restore_render(cmd: PasswordsManagerCommand, default_screenshot_path: Path): + # Triggering a restore makes the device show "Restore password list?". + # Capture that approval screen, then approve. The payload is a small valid + # metadata blob (one entry), shared with the load test vectors. + metadatas = bytes.fromhex("02000761060007616c6c6168") + cmd.load_metadatas(metadatas, compare=(default_screenshot_path, "restore")) + cmd.reset_approval_state() diff --git a/tests/functional/touch/navigator.py b/tests/functional/touch/navigator.py index d94beb8..dc26993 100644 --- a/tests/functional/touch/navigator.py +++ b/tests/functional/touch/navigator.py @@ -1,3 +1,7 @@ +# CustomTouchScreen builds its members (home, settings, button, ...) through the +# ragger MetaScreen metaclass, so pylint cannot see them statically and reports +# false no-member errors on every self.screen.* access. +# pylint: disable=no-member from enum import auto from functools import partial from time import sleep @@ -34,8 +38,10 @@ class CustomNavInsID(BaseNavInsID): CHOOSE_KBL_QWERTY = auto() # startup disclaimer DISCLAIMER_CONFIRM = auto() - # Approve metadatas + # Generic "select/validate" tap (settings switches, BARS_LIST entries, ...) BUTTON_APPROVE = auto() + # Approve an APDU confirmation prompt (nbgl_useCaseConfirm) + APDU_APPROVE = auto() class CustomTouchNavigator(Navigator): @@ -63,6 +69,7 @@ def __init__(self, backend, device, golden_run): CustomNavInsID.CHOOSE_KBL_QWERTY: partial(self._choose, 1), CustomNavInsID.DISCLAIMER_CONFIRM: self.screen.disclaimer.confirm, CustomNavInsID.BUTTON_APPROVE: self.screen.button.tap, + CustomNavInsID.APDU_APPROVE: self.screen.button.tap, } super().__init__(backend, device, callbacks, golden_run) From f044dd8cdf431813e0df0535ca208cfb28c9c889 Mon Sep 17 00:00:00 2001 From: Charles-Edouard de la Vergne Date: Fri, 19 Jun 2026 16:00:48 +0200 Subject: [PATCH 08/10] Add ragger test for showing a password on a later list page On touch devices the passwords list is paginated and NBGL forwards a page-relative index, so password_callback() rebuilds the absolute index from the page number. Add a regression test that seeds enough passwords to span two pages, shows the first entry of page 2, and asserts the revealed value matches the value derived independently for that nickname (guarding against an off-by-page error in the recalculation). Marked skip_nano since the Nano list shows one choice per page. --- tests/functional/test_passwords.py | 83 ++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/tests/functional/test_passwords.py b/tests/functional/test_passwords.py index b7f2301..ab8b6a3 100644 --- a/tests/functional/test_passwords.py +++ b/tests/functional/test_passwords.py @@ -1,9 +1,28 @@ from pathlib import Path +from time import sleep +import pytest +from ragger.backend import BackendInterface from ragger.navigator import Navigator, NavIns, NavInsID +from passwordsManager_cmd import PasswordsManagerCommand from touch.navigator import CustomNavInsID +# SETS byte and entry layout reused from EXISTING_METADATA in tests_vectors.py: +# [DATALEN=0x0a][KIND=0x00][SETS=0x07][9-char nickname]. The SETS byte also +# selects the charsets used by the password derivation, so the expected value +# of an entry can be recomputed independently with generate_password(SETS, name). +_SETS = 0x07 + + +def _metadata_entry(name: str) -> bytes: + assert len(name) == 9, "nickname must be 9 chars to match the fixed DATALEN" + return bytes([0x0a, 0x00, _SETS]) + name.encode() + + +def _screen_texts(backend: BackendInterface) -> list: + return [event["text"] for event in backend.get_current_screen_content()["events"]] + def test_delete_one_password(navigator: Navigator, default_screenshot_path: Path): instructions = [ @@ -64,3 +83,67 @@ def test_create_password(navigator: Navigator, default_screenshot_path: Path): "create_password", instructions, screen_change_before_first_instruction=False) + + +@pytest.mark.skip_nano # Nano lists one choice per page; this targets the touch pagination +def test_show_password_on_second_page(cmd: PasswordsManagerCommand, + navigator: Navigator, + custom_backend: BackendInterface, + default_screenshot_path: Path): + """Showing a password located on page >= 2 of the list must reveal that + exact entry's value. + + On wallet devices the choices list is paginated and NBGL forwards a + page-relative index, so password_callback() rebuilds the absolute index + from the page number (page * nbPasswordsPerPage + index). This test guards + against an off-by-page regression in that recalculation: it seeds enough + passwords to span at least two pages, selects the first entry of page 2, + and asserts the shown value matches the value derived independently for + that nickname. + """ + # Seed enough 9-char nicknames to guarantee more than one page on every + # wallet screen size (Flex shows 4 per page, larger screens a few more). + names = [f"pwd{i:06d}" for i in range(12)] + metadata = b"".join(_metadata_entry(name) for name in names) + + navigator.navigate([ + CustomNavInsID.DISCLAIMER_CONFIRM, + CustomNavInsID.CHOOSE_KBL_QWERTY, + ], screen_change_before_first_instruction=False) + cmd.load_metadatas(metadata) + + navigator.navigate([ + CustomNavInsID.HOME_TO_MENU, + CustomNavInsID.MENU_TO_DISPLAY, + ], screen_change_before_first_instruction=False) + sleep(0.5) + + # Derive the per-page count from page 1 (the title is not a list entry). + page1 = [t for t in _screen_texts(custom_backend) if t in names] + nb_per_page = len(page1) + assert nb_per_page >= 1, "no password entry rendered on the first page" + assert nb_per_page < len(names), "all passwords fit on a single page; seed more" + assert page1 == names[:nb_per_page], "first page must list the entries in order" + + # The first (top) entry of page 2 is the absolute index `nb_per_page`. + expected_name = names[nb_per_page] + expected_value = cmd.generate_password(_SETS, expected_name) + + navigator.navigate([CustomNavInsID.SETTINGS_NEXT], + screen_change_before_first_instruction=False) + sleep(0.5) + assert expected_name in _screen_texts(custom_backend), \ + "page 2 must start with the entry following the last entry of page 1" + + navigator.navigate_and_compare(default_screenshot_path, + "pwd_on_second_page", + [NavIns(CustomNavInsID.LIST_CHOOSE, (1, ))], + screen_change_before_first_instruction=False) + sleep(1.0) + + # The "Your Password" screen shows the nickname and its derived value; both + # must correspond to the page-2 entry, proving the absolute index is right. + shown = _screen_texts(custom_backend) + assert expected_name in shown + assert expected_value in shown, \ + f"expected value of '{expected_name}' not shown; wrong entry selected" From 99188fddff5fe0ca66a204de8333a0cad215fa82 Mon Sep 17 00:00:00 2001 From: Charles-Edouard de la Vergne Date: Thu, 18 Jun 2026 18:38:34 +0200 Subject: [PATCH 09/10] Add new snapshots --- .../snapshots/apex_p/backup/00000.png | Bin 0 -> 2628 bytes .../apex_p/pwd_on_second_page/00000.png | Bin 0 -> 3016 bytes .../apex_p/pwd_on_second_page/00001.png | Bin 0 -> 2499 bytes .../snapshots/apex_p/restore/00000.png | Bin 0 -> 2609 bytes .../functional/snapshots/flex/backup/00000.png | Bin 0 -> 9367 bytes .../snapshots/flex/pwd_on_second_page/00000.png | Bin 0 -> 9809 bytes .../snapshots/flex/pwd_on_second_page/00001.png | Bin 0 -> 8663 bytes .../functional/snapshots/flex/restore/00000.png | Bin 0 -> 9260 bytes .../functional/snapshots/nanox/backup/00000.png | Bin 0 -> 337 bytes .../snapshots/nanox/restore/00000.png | Bin 0 -> 334 bytes .../functional/snapshots/stax/backup/00000.png | Bin 0 -> 8811 bytes .../snapshots/stax/pwd_on_second_page/00000.png | Bin 0 -> 10406 bytes .../snapshots/stax/pwd_on_second_page/00001.png | Bin 0 -> 7795 bytes .../functional/snapshots/stax/restore/00000.png | Bin 0 -> 8666 bytes 14 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/functional/snapshots/apex_p/backup/00000.png create mode 100644 tests/functional/snapshots/apex_p/pwd_on_second_page/00000.png create mode 100644 tests/functional/snapshots/apex_p/pwd_on_second_page/00001.png create mode 100644 tests/functional/snapshots/apex_p/restore/00000.png create mode 100644 tests/functional/snapshots/flex/backup/00000.png create mode 100644 tests/functional/snapshots/flex/pwd_on_second_page/00000.png create mode 100644 tests/functional/snapshots/flex/pwd_on_second_page/00001.png create mode 100644 tests/functional/snapshots/flex/restore/00000.png create mode 100644 tests/functional/snapshots/nanox/backup/00000.png create mode 100644 tests/functional/snapshots/nanox/restore/00000.png create mode 100644 tests/functional/snapshots/stax/backup/00000.png create mode 100644 tests/functional/snapshots/stax/pwd_on_second_page/00000.png create mode 100644 tests/functional/snapshots/stax/pwd_on_second_page/00001.png create mode 100644 tests/functional/snapshots/stax/restore/00000.png diff --git a/tests/functional/snapshots/apex_p/backup/00000.png b/tests/functional/snapshots/apex_p/backup/00000.png new file mode 100644 index 0000000000000000000000000000000000000000..714d2e5c571a3afcab43901671d7e86537b35da6 GIT binary patch literal 2628 zcmeHJX*3&%77ku9S~QKNQ%g!pN5xh((l(Y_ibK&_Tg1|qm#HO|qPC6^O0||}Du|_} zsAp>H*dhzjP+~cxA+MH*B}$N3(jb;+=EuBW@635`e$M>3Kkj$#x%Zs=-Sgcq!`abB zTI!4x005A-v$eVf0Ej@3B1cm62*E1$A0<-9?X0f3#ul$mO+;g1fV2^PZSGpE43x9P zF;(BDWqCUwEB#wbTwG;QFj^uV1>?l7c~`V}RY-6_S(T3rIrItQ$9Yqr%xl`4%I>ZV zl$_7v^DD}Ql23S@+Qup})apZ!UXHm-ko$AAy7&%(hY92TA;6oKPxq-B zl>9m7rs@Q6;TamTe97}`vEFlJH8^jbu~sExkFNOq>6NIBCOJ83**Q4{#PTePdDFSx ze)WnvNmu9rWWAIT@0_!DC#Y+wD|<{Rz1H1ou!7-)TDbQ|#X)cEM=1M}>U;VcM!2dn zYxGHW;vYER2JV)Kd3?QW+FrUW2(QGFB<;++8-guJ)vw%j`reTgjGwdJUQAEzlK2eLKKFq#Bm0ZOUAGD4w`!vSDJZc zX3Bb#6`l4Hs386Z?N;0190kz5)nXEoS0bZGHaL@4XHzHmsjM~o}Xu+xFAGaX89f-<4xfQb|}PNiSnpN zTug(qi{mX_`T}kQBVLXq)uXyWqjQ(Py!VpJ^e`g$0uoGwgUuNmw5Ef=Rzzz;Bf$qA z>A|y$akLFnsjlE~(@R1N6L7WF3(W(&rGsp5j$h+HFsZ`>daBN`)1{b4WxUb-eL}|C zv_ms5(6SlJG(8?R@FOF^-A&JZaYl5C>3aUCS2kp2+qW>3i0W50&sg*wQ*icg%ObAzh@?AGt+$&Pv$dSU z<=OS+6s-}IOG%5_*7L%pFf0|iHYVp-j-Y}Q__0$dA^E<~u!1}pcRe$m)W!Ls8trkE zp7i;ju^e~hh0gs-(Gi;aV67x?MVy$?d z+#b$)q&*?aWAc^p@(DotrfNd( z?{wYqxnV@Rd5m0zyM)(7H&B6R#98LnXRrUS`q05gK2Y{dU}rm>^UKct^)Xge_~6;) zv@PcOj-kyxQ;3fqZ}$OT6gy#t2-jf_VY zW!V(iABUz5@_B9T^8u&fQx%%HVbVl~P* z-?{v@p-GiNSYqX36)<=#%veG*FLx|0_PiNsFLW~_Y;@@VfsgY6(K5~i!o7m__XrM7l5so2Q&0~ z9c;jE22WNRY5ZENAD*E_>UXBuLQu(tsSfI=cvA7jQ!+u}%%+5oajAqP{5=R=xS$CP zEGAZaOq=iQ4-AI5b208#u$;lK{QIaMC|-&hUNA%@igfAbj-iXW_HTUrf0m3vig<5e V`+z)LedG=ScGiwobrya<{2Otgg1rC$ literal 0 HcmV?d00001 diff --git a/tests/functional/snapshots/apex_p/pwd_on_second_page/00000.png b/tests/functional/snapshots/apex_p/pwd_on_second_page/00000.png new file mode 100644 index 0000000000000000000000000000000000000000..519ff062700c55473cc818f37204ecffd0865a06 GIT binary patch literal 3016 zcmds3c{CLI9@nB|E8+^FiHdC1rNN{rOejn0-f(THY*|LOscbK1xFu^D!f=tL#*AgG zlOYt}F&ej4@a5zH{Gsr|v!7_x^i-{LcCPKIeRY|9rOZ{fjo{5~2#C zLPA0kmKJ80goL(eZJrd7t(!=;+`G+(ShA(r*&lA_%+dy{3M_;mb`5p{0da9#@Aj6I zV)5MV19z()CzSeG-Yb~qZ3vI97s&}lp+o>y@I8l3MD_yU_xH4#iMStxACof4{3|%j z6YesAG61efl|T$+N=;Wj7)setNq~q^`X|TE=fM)M>3Y~5SN>tElsLy04)Cvoa!tYv z(z;qkWk8|c26wqz#l)YlX7Qa`<7EwW1S@zv_609t?J$QN08))`xOX{{ULLR`1J>=j z6cWQytXi80`eC`(7;OtgMb#75;+<3+yYyIKXO~RO>r8D1l(Lr*bZ8}HF6;#`Qj^F) z_z?j5$+<7o5E?|mdTY>>Ke0n4?A!Ye* z2Q=cCjMN&RAF8O z)T;{LJ;a(8&{&N`v?K+0$T@7Kkqh(XwEFT`&{?QD7{B%*wYIP|=90|j0=irkx#haWS(vwU-*zgGbHBt~t+ z7(XI~i^lJKm#plZxz?Z*A)9xaN##CW9|yB?{0Ep^(#z29RRJ7#-L0f^Z`3Ov0WJy; zQZ9v!>3hnEfa)m<->iv06tsaHs0?Y705;uiL%1Ix2FiS!rv8}!69sN;ik0o&c#}W9 zWW>cU>BR1aS}kN7p;6~l_q#6p4YqR&DmpRN3zLtlJ<%^)G*l*x4?0Jx)=K-|$RgjT zM$`>fEe)c0PlhLf?hS8GmN1$`nHh{+_ZTb)e~5~MRZrtTO`7A(VRW2xeugP&(&_z} z9^$^crvUI^(5Q~-)zgoB6^E$LT3Rj3f1l5U^992?=UME;Nt*IY&lClos&i6@XFI@` zk-OH=a@i%Q-O3u0tlQ<8a^A)%R#qQe)orvMUScW(JP~yY&CBsqeq!XsnV6?uTU9Jy zN0%R~Ti$J9F`Zhh*v873yzg~Zi6V{dzm$1(DYuCu2J(9sO`Xwmf+8+*Je&>PRB{h5 zrShe0GB_iBT3$a6@57$9_a)7V6Kig*z1Og<9Dk_jHnsi>?xf*`u+B5-)3rvDc4sy{ zUDhkG6=`lmexuR9M#>EI&a+4$Es%)@I?wN^5;y<*`SI0n-R^D~QEo@~H@lVxmpgGz ztjx9RH9x~qeDqow9z96^2whPk?Twy&v|mJ5zqz3SYa78Fj)uOvQlM?6+UoUDAto23 zM<794gC0rPs5UrhJ`9NqkK2W3PayqzhI_Ho?IkK*%0rPoK?qk#>k{bMY|UtU%@QfWv^>aaAtvz;pv`XY3cpqg>mKf9iPo(r+Ha8UGo5%l z)vok#G@8{Zz@dFQ+)7l9e1y^|hvuIyxW|yg*P3)^Dl`QCIFcVhy;+>&vA*9ewB*t( zZv3iXK3pv?Bb?mLeK&hjIYFYyBE8SVDa`cc3Q`J}(@hcD*;q)k3HFK&Hl{Lmf~ zO>iA|tsC~3VKA+Az=PHQiw|-{jQemIiogXugK!?<)knd;Ay`1@&r2^sks>BZ4@cS z)|b$l1)Cj57mb;J=jLE5tb?x>F3^@wV^5gZ&g~C*EC0Q|tZ{L5)kbt1IP^^#sb6RDO&2e@baj|ksd6J- z1Td3BC?rA?Sm%CebX4!ebj3=re1@BT=RA5i?=$B#(Asi!874Lg*L?KfoX7TsKVg%b z*)pZZJ9!e~^O^5enAl&%B%-GiW? zPG3Ap(-BN*c4GM3q|xJC8CR{kW1IGdjr^19FYHYueH1_avOcJ3%BG0o6%8k`K=pb= z$8=yEx`#~BgR1=c9nzP%n14CPf|>eiiZ4o|Vqm{yRri2!yg1_XaRYVuPC-x{xKr#tdt6HRd<^)QS-R literal 0 HcmV?d00001 diff --git a/tests/functional/snapshots/apex_p/pwd_on_second_page/00001.png b/tests/functional/snapshots/apex_p/pwd_on_second_page/00001.png new file mode 100644 index 0000000000000000000000000000000000000000..18b3017409bb2c50b831b3b81bd600aa368b90a7 GIT binary patch literal 2499 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 Hjl2H@mcw&+ literal 0 HcmV?d00001 diff --git a/tests/functional/snapshots/flex/backup/00000.png b/tests/functional/snapshots/flex/backup/00000.png new file mode 100644 index 0000000000000000000000000000000000000000..6ebca970e21499aaf6e3b35ef2f8134ddf873e02 GIT binary patch literal 9367 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 P1hTcZJBvNzk^DaZXBWY# literal 0 HcmV?d00001 diff --git a/tests/functional/snapshots/flex/pwd_on_second_page/00000.png b/tests/functional/snapshots/flex/pwd_on_second_page/00000.png new file mode 100644 index 0000000000000000000000000000000000000000..db25bcd94e4d434dbee3c0998fc9c39ddf8058fc GIT binary patch literal 9809 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 VF|P`kz*aF3#N77G>r+=_{sWGG+-Lv* literal 0 HcmV?d00001 diff --git a/tests/functional/snapshots/flex/pwd_on_second_page/00001.png b/tests/functional/snapshots/flex/pwd_on_second_page/00001.png new file mode 100644 index 0000000000000000000000000000000000000000..b9692397fa210d5e7f99a1d297e17a871b7a29f7 GIT binary patch literal 8663 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 literal 0 HcmV?d00001 diff --git a/tests/functional/snapshots/nanox/restore/00000.png b/tests/functional/snapshots/nanox/restore/00000.png new file mode 100644 index 0000000000000000000000000000000000000000..623773afeab8b778d12fcc339ac3708e78d90345 GIT binary patch literal 334 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|07Jm$ zo3ji4D}UAdotVV2_;JAP89UzBO60JoaW%z?0d+yazy9#&@1pX=(i48CWEDM2+tk1A zd(D$9RqlZIMm7Jc_s+U@t6=xG>)C(4ZTWqXzoWK)7yr>OvdO#SqV1%oR=zm>uG>=5 zYTIPDORqKTZl3u0Z*|K3yvL=hwk0uEs&1^#WPf8fZ}-(H(@cCnmVKD;*UL~`GB0J+`$LTQ|IYZznE&m>{>^(kl{@k`z`Vx5 aU~^i2ZIQNmtNzm+AVE)8KbLh*2~7YBpqXd@ literal 0 HcmV?d00001 diff --git a/tests/functional/snapshots/stax/backup/00000.png b/tests/functional/snapshots/stax/backup/00000.png new file mode 100644 index 0000000000000000000000000000000000000000..86983248cfbaba881820ed847d7db887bcf75703 GIT binary patch literal 8811 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 literal 0 HcmV?d00001 diff --git a/tests/functional/snapshots/stax/pwd_on_second_page/00001.png b/tests/functional/snapshots/stax/pwd_on_second_page/00001.png new file mode 100644 index 0000000000000000000000000000000000000000..272751a70ebba19bbdb532e842dcbfe9ccd3ad1b GIT binary patch literal 7795 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 TUsr%{Hn^ Date: Thu, 18 Jun 2026 18:48:11 +0200 Subject: [PATCH 10/10] Update CHANGELOG --- CHANGELOG.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 844c576..af4295e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,16 +5,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.3.3] - 2026-06-18 +## [1.3.3] - 2026-06-19 ### Fix +- Restore no longer corrupts password lists larger than ~255 bytes. - Demo passwords from the developer `POPULATE` build are seeded only once (on first storage initialization). +- Use correct `printf` format specifiers for `size_t` values (CodeQL). ### Change - Rebuild the backup Web UI on Vite + React 18 with Ledger's lumen design system; built with pnpm on Node 22. - Publish the Web UI through GitHub's native Pages workflow. +- Reword the device confirmation prompts for the Backup/Restore actions. +- Web UI: save the backup through the browser's native save dialog +- Web UI: drop the (constant) storage-size row from the device card. +- Only compile the test APDU handler when built with `TESTING=1`, so production builds contain no test code. + +### Add + +- Unit tests for the metadata write offset and capacity limits +- ragger tests for the backup/restore confirmation screens. +- ragger test checking that showing a password located beyond the first list page selects the right entry on touch devices. ## [1.3.2] - 2026-06-05