|
1 | 1 | # Changelog |
2 | 2 |
|
3 | | -## [2.4.0] — May 2026 |
| 3 | +## [2.4.1] — August 2026 |
| 4 | +### Added |
| 5 | +- **Send Login Before Password** — new per-password option in Password Manager; when enabled, device types the login username/email first, then presses Tab or Enter (configurable), waits a specified delay (default 300 ms), and finally types the password. Useful for automated login to forms requiring both username and password fields. Configurable per entry in web cabinet: login field, navigation mode (Tab/Enter), and delay before password. LOGIN ⏎ or LOGIN ⇥ badge shown on device display and in web cabinet depending on navigation mode. |
| 6 | +- **Wildcard Passwords** — randomly generated passwords (3-50 chars, default 16) that never leave the device. Generated on-demand per boot session with cached reuse; ideal for accounts where only HID typing is needed, not clipboard access. Singleton constraint enforced (max 1 wildcard entry). Wildcard entries cannot be copied to clipboard or exported (UI guards in web cabinet and offline decrypt tool). Session cache zeroed using `String::setCharAt()` at shutdown, mode transitions, and post-transmission. RND badge (magenta) shown on device display. |
| 7 | +- **Secure Import/Export Mode** — TOTP keys and passwords are now exported/imported exclusively through an isolated, closed-AP session (reboot → PIN unlock → freshly generated, one-time WPA2 access point), replacing the old always-on 5-minute API-enable toggle. Deliberately outside the ordinary 8-layer web cabinet stack — smaller attack surface for a sensitive bulk-data operation, bounded by a 10-minute rolling timeout and a 30-minute hard cap. Import path validates the decrypted file's schema (TOTP keys vs. passwords) before writing, rejecting a mismatched file type with an explicit error instead of silently corrupting the store. Mirrors whichever Hidden Space is active — tagged to the requesting space so the mode only triggers for the space that requested it. |
| 8 | + |
| 9 | +### Changed |
| 10 | +- **BREAKING:** Removed the legacy 5-minute "enable import/export" toggle and all six of its endpoints (`/api/enable_import_export`, `/api/import_export_status`, `/api/export`, `/api/import`, `/api/passwords/export`, `/api/passwords/import`) along with their tunnel/obfuscated variants. There is no replacement at these paths — all import/export now goes exclusively through Secure Import/Export Mode (see `### Added` above and `docs/development/ENDPOINTS.md`). |
| 11 | +- **Password flags migration** — `auto_send` flag migrated from standalone `bool` to bitmask (`uint16_t flags` in `PasswordEntry` struct). Existing entries are automatically migrated on first boot. No user action required. |
| 12 | +- **Heap pressure reduction** — `KeyManager::getAllKeys()` and `PasswordManager::getAllPasswords()` now return `const std::vector<T>&` instead of by-value copies, eliminating repeated full-vector allocations (previously up to ~8-20 KB per call, called as often as every 250ms from the main loop TOTP display). Sorting by `order` moved from the getters into `loadKeys()`/`loadPasswords()`, `reorderKeys()`/`reorderPasswords()`, and `replaceAllKeys()`/`replaceAllPasswords()` (import), since the getters no longer mutate state. |
| 13 | +- **Encrypted transport encoding** — the secure request/response envelope (`data`/`iv`/`tag` fields) now uses base64 instead of hex encoding, reducing payload size by ~30%. This is a protocol-breaking change: server and client must be updated together; existing sessions are invalidated on upgrade. |
| 14 | +- **Removed legacy `firmware/` directory** — firmware binaries have been served from `website/public/firmware/` (published via GitHub Pages) since the previous release; the old root-level `firmware/` copies were stale leftovers with no remaining references in the build workflow, web flasher, or documentation. |
| 15 | + |
| 16 | +### Fixed |
| 17 | +- **TOTP/password reorder consistency** — fixed a latent bug where `reorderKeys()`/`reorderPasswords()` and key/password import did not re-sort the in-memory list after changing entry order, relying on the (now removed) sort-on-every-read behavior of the getters. Reordering and import now sort explicitly, so the new order is reflected immediately without requiring a reboot. |
| 18 | +- **Password export buffer overflow** — increased `decryptWithPassword()` JSON buffer from 2KB to 16KB to support exporting 50+ password entries. Previously, exports exceeding ~15-20 passwords would fail silently, returning `{"ciphertext": null}` or empty responses due to insufficient buffer space for base64-encoded ciphertext. The new limit supports up to ~100 password entries safely. |
| 19 | +- **Hidden Space files orphaned on removal** — `wipeHiddenSpace()` derived the HMAC paths of Space B's private files (`keys`, `passwords`, `wifi`, `session`, etc.) using whichever device key happened to be loaded, which was always Space A's key at every real call site. Since those paths are only correct when derived from Space B's own key, the files were never actually found or deleted — despite logs and the web response claiming a full wipe. Replaced with `removeHiddenSpaceWithPin(spaceBPin)`, which uses the existing non-destructive `tryDecryptSlotDry()` to obtain Space B's key into a local buffer without disturbing the caller's active Space A session, then addresses and deletes the files correctly. The sentinel file (intentionally addressed via Space A's key by design) is unaffected by this fix. |
| 20 | +- **Password screen not redrawn after QR code dismissal** — displaying a TOTP key's QR code (`/api/show_qr`) while the device was in Password mode left the screen blank after the 30-second auto-hide timer expired, or after manual dismissal via button press. TOTP mode already had an internal redraw flag consumed on its next code-refresh cycle; Password mode had no equivalent, so its own change-detection condition (index/revision unchanged) never fired. Added a mirrored, self-clearing redraw flag consumed on the next Password-mode loop iteration. |
| 21 | +- **False RTC "drifted" status on timezone mismatch** — `GET /api/rtc` serialized the RTC's UTC epoch through `localtime_r()` before sending it, embedding the device's configured timezone offset into a timestamp string with no UTC indicator. The browser then parsed that string in its own local timezone, so the client-side drift calculation silently combined two independent timezone offsets whenever the device timezone was unset or didn't match the browser's. Switched to `gmtime_r()` with a trailing `Z` suffix across all three response paths (standard, tunneled, obfuscated), so the client always receives and parses a true UTC timestamp. |
| 22 | +- **Duress PIN left as an orphan after disabling startup PIN** — the Duress PIN hash file and its enabled flag were untouched by `disablePinEncryption()`, so a configured Duress PIN remained on disk and "active" even though startup PIN's lock screen — its only possible entry point — no longer existed. Disabling the startup PIN now also removes `/duress_pin.hash`. |
| 23 | + |
| 24 | +### Security Fixes |
| 25 | +- **PIN length disclosure mitigation** — PIN entry mask now displays fixed-width |
| 26 | + (10 slots) regardless of configured PIN length, preventing visual shoulder-surfing |
| 27 | + attacks. Previously, the number of dots/stars on screen revealed the exact PIN |
| 28 | + length. Auto-submit timing may still reveal approximate length to a live observer |
| 29 | + (documented known limitation, accepted UX trade-off for two-button interface). |
| 30 | +- **Startup PIN can no longer be disabled while Hidden Space exists** — disabling |
| 31 | + the startup PIN silently attempted to wipe Hidden Space using the wrong key (see |
| 32 | + Fixed above) and would have left an inconsistent, partially-decrypted device |
| 33 | + state. Disabling PIN is now hard-blocked, both server-side (`400 pin_required`-style |
| 34 | + early rejection before the on-device prompt) and on-device, while Hidden Space is |
| 35 | + provisioned; the web cabinet greys out and disables the toggle with an inline |
| 36 | + explanation. Removing Hidden Space is a separate, explicit action that now requires |
| 37 | + entering Space B's own PIN on-device before any deletion occurs. |
| 38 | +- **Hidden Space creation on an unencrypted device key** — `createHiddenSpace()` |
| 39 | + wrote its dual-slot structure at a fixed file offset without checking that the |
| 40 | + existing device key file was already in PIN-encrypted format. On a device with |
| 41 | + the startup PIN disabled (33-byte unencrypted key file), this silently corrupted |
| 42 | + the key file instead of failing. Added an `isDeviceKeyEncrypted()` guard at every |
| 43 | + entry point — all three web handlers, the on-device provisioning flow, and inside |
| 44 | + `createHiddenSpace()` itself as defense-in-depth — plus a greyed-out, disabled |
| 45 | + Hidden Space section in the web cabinet when the startup PIN is off. |
| 46 | +- **Duress PIN configurable without startup PIN enabled** — `POST /api/duress_pin_update` |
| 47 | + had no dependency on startup PIN state, allowing a Duress PIN to be set up even |
| 48 | + though it can only ever be triggered from the startup PIN lock screen. Added an |
| 49 | + `isDeviceKeyEncrypted()` guard to all three web handlers (`400` early rejection), |
| 50 | + plus a greyed-out, disabled Duress PIN section in the web cabinet, matching the |
| 51 | + Hidden Space guard pattern above. |
| 52 | + |
| 53 | +### Documentation |
| 54 | +- Updated [API Endpoints](docs/development/ENDPOINTS.md) — added `send_login`, `login`, `nav_mode`, `login_delay_ms` parameters to `/api/passwords/*` endpoints; added `wildcard` and `wildcard_len` parameters with immutability notes for update operations |
| 55 | +- Updated [User Guide (EN)](docs/user/GUIDE.html) — added "Send Login Before Password" section with navigation mode explanations; Wildcard Passwords section already present |
| 56 | +- Updated [User Guide (RU)](docs/user/GUIDE.ru.html) — added "Отправка логина перед паролем" section; Wildcard-пароли section already present |
| 57 | +- Updated [Decrypt Export Guide](docs/user/decrypt-export-guide.md) — Wildcard passwords section with copy/export restrictions already present |
| 58 | +- Updated [Security Overview](docs/development/security/SECURITY_OVERVIEW.md) — |
| 59 | + added PIN length timing disclosure to Known Limitations section; added wildcard password RAM zeroing limitation (`setCharAt()` vs `secure_memzero()` distinction) |
| 60 | +- Updated [Security Model](docs/development/security/security_model.md) — |
| 61 | + added PIN length via timing to Threat Model (Out of Scope); added comprehensive Wildcard Passwords section covering singleton constraint, session-based generation, RAM zeroing points, and platform limitations |
| 62 | +- Updated [System Design](docs/development/system_design.md) — clarified |
| 63 | + `/.sys_ui_prefs` file description regarding fixed-width mask implementation; added ENT, L/T, L/E, and RND badges to Password Security Badges table; updated `secureShutdown()` section with wildcard session wipe details |
| 64 | +- Updated [Modes Guide](docs/user/MODES.md) — added note on wildcard password behavior consistency across network modes with export restriction clarification |
| 65 | +- Updated [README.md](README.md) — added Wildcard passwords to Password Manager feature list |
| 66 | +- Updated [ENDPOINTS.md](docs/development/ENDPOINTS.md) — replaced the legacy 6-endpoint import/export API reference with Secure Import/Export Mode's routes |
| 67 | +- Updated [SECURITY_OVERVIEW.md](docs/development/security/SECURITY_OVERVIEW.md) — added "Import/export security model" section explaining the closed-AP, no-Layer-4 trade-off |
| 68 | +- Updated [README.md](README.md) — clarified bulk import feature description to reference Secure Import/Export Mode |
| 69 | +- Updated [Security Overview](docs/development/security/SECURITY_OVERVIEW.md) — corrected Hidden Space section: PIN disable is now blocked (not auto-wiped) while Hidden Space is provisioned |
| 70 | +- Updated [Security Model](docs/development/security/security_model.md) — replaced references to `wipeHiddenSpace()` with `removeHiddenSpaceWithPin()`, corrected the calling-context description |
| 71 | +- Updated [API Endpoints](docs/development/ENDPOINTS.md) — corrected `/api/hidden_space` disable action: removed the inaccurate "must be called from Space B context" claim, documented the new `prompt_on_device` deferred-confirmation response |
| 72 | +- Updated [User Guide (EN)](docs/user/GUIDE.html) and [User Guide (RU)](docs/user/GUIDE.ru.html) — corrected Hidden Space section: PIN disable is blocked, not automatic, while Hidden Space is active |
| 73 | +- Updated [Modes Guide](docs/user/MODES.md) — corrected Hidden Space constraint description to match blocked (not automatic-wipe) behavior |
| 74 | + |
| 75 | +--- |
| 76 | + |
| 77 | +## [2.4.0] — June 2026 |
4 | 78 | ### Added |
5 | 79 | - **Hidden Space** — second independent encrypted vault selectable at boot via |
6 | 80 | alternate PIN. Each space has isolated TOTP keys, passwords, web cabinet |
|
0 commit comments