First stable release of the OTPClient 5.0 series — a complete GUI rewrite on GTK4 + libadwaita, multi-database support, token grouping, an opt-in trigger keyword for the desktop search provider, and a full sweep of crypto/import-path hardening across the shared core. Existing v4.x databases unlock and migrate automatically.
For the per-milestone changelog see the alpha/beta/rc release notes (v5.0.0-alpha1 → v5.0.0-rc1). What follows is the upgrade-from-4.x summary.
New
- Complete GTK4 + libadwaita UI rewrite. Zero deprecated APIs. GtkColumnView token list, AdwDialog forms, GtkFileDialog async, AdwOverlaySplitView sidebar. AdwDialog-based shortcuts view replaces GtkShortcutsWindow.
- Multi-database support with sidebar management, primary-database checkmark, and right-click "Move to…" between databases. Dedicated "No database yet" page on fresh install.
- Token grouping (e.g. "Work", "Personal") with header-bar dropdown,
group:/#search prefix, right-click assignment. Groups round-trip through Aegis/AuthPro/2FAS import/export. - Cross-database search across every loaded database, with auto-select-and-copy when results narrow to one row.
- Hidden-by-default OTPs with click-to-reveal and auto-hide.
- Async unlock with KDF spinner — the main loop no longer blocks while Argon2id derives the key.
- Search-provider trigger keyword (default
otp) so OTP results no longer drown under file/app runner output. KRunner subtitle no longer leaks live codes.ActivateResultnow copies the OTP to the clipboard. - Welcome and What's New dialogs covering the redesign, groups, shortcuts, and search.
- Native database backup as the default Settings → Backup action; format-specific export becomes migration-only with a plaintext warning.
- Settings import/export via CLI and GUI (JSON, capped at 1 MiB).
- CLI —
--output=table|json|csvforshow/list/list-databases, translated user-facing strings,--list-databases, HOTP counter in CSV, bash/zsh/fish completions,--password-filerefuses group/world-readable files (O_NOFOLLOW). - UX — paste-to-fill
otpauth://URI, KDF presets, friendly timeouts, backup-age banner with snooze and real-time clock, lock-time clipboard wipe, plural-correct strings, technical-field tooltips, countdown color pickers, optional level bar instead of seconds, drag-and-drop polish. - Tray — StatusNotifierItem D-Bus protocol replaces libayatana-appindicator (broader DE compatibility).
- Performance — KDF-derived key cache, lowercased entry fields for filter, deferred HOTP writes, lazy cross-DB OTP, pre-folded labels in the search provider.
Security
- Argon2id header validation on unlock (refuses iter / memcost / parallelism outside
ARGON2ID_MIN/MAX_*bounds — closes a memory-exhaustion / KDF-weakening DoS vector). - KDF byte-length fix —
gcry_kdf_*was being passedg_utf8_strlen(character count) instead of byte count, weakening keys for non-ASCII passwords. Existing v2 databases unlock via a transparent retry path and are silently re-encrypted on the next write. O_NOFOLLOWeverywhere —path_open_safe_regular_file()(O_RDONLY | O_NOFOLLOW | O_CLOEXEC+fstatS_ISREG) on every importer (Aegis, AuthPro, 2FAS, FreeOTP+) and both database read sites. Subsequent reads via/proc/self/fd/<n>close the symlink-swap TOCTOU window.chmod 0600on database backups so a permissive umask cannot leave.bakfiles group/world-readable.- Core-dump suppression via
prctl(PR_SET_DUMPABLE,0)+RLIMIT_CORE=0so a crash with secrets in memory cannot leak them to disk. otpauth://URI capped at 4 KB, HOTP counter capped at 2^48, PNG QR capped at 4096×4096.- Search-provider hardening — refuses
Match,Run,ActivateResult,GetInitialResultSet,GetSubsearchResultSetwhen the keyword is empty (closes arbitrary local D-Bus enumeration of accounts). - AEAD validation —
gcry_cipher_checktagnow catches every failure (was returning unverified plaintext on non-checksum errors). 2FAS no longer silently accepts plaintext on tag mismatch. - HOTP counter increment is now transactional in the CLI (rolled back if
update_dbfails) so a failed save no longer desyncs the counter. - AuthPro / 2FAS / FreeOTP+ writes NULL-check
g_file_replace; secure-buffer leaks closed; allocator mismatches fixed (gcry_freeforjson_dumps()output throughout). - Aegis export plaintext now lives in secure heap.
- Clipboard wipe on
SIGINT/SIGTERM/SIGHUPand on dispose. - Settings import capped at 1 MiB.
- Secret service disabled by default; purpose clarified in dialogs.
- HOTP dedupe is now collision-aware via
json_equal; the 32-bit hash no longer silently drops distinct tokens on import when two entries collide.
Fixes (selection)
- Use-after-free in async secret lookup; double-free of
filter_modelin window dispose; DBus assertion on exit. - 2FAS importer NULL-derefs on missing
servicesEncrypted, malformed payload, undersized AEAD ciphertext; secure-buffer leaks on decrypt and tag-check failure. - Aegis importer NULL-deref when
header.slotsis missing or the password slot lackskey_params. - AuthPro importer leaked
GFile/GFileInputStreamon the plain-backup path. - Right-click row selection no longer requires a prior left-click.
- Notification spam suppressed during store rebuilds and search-bar close; stable notification IDs.
- Group dropdown now restored on startup; window size persisted across sessions.
- Schema now compiled on install; icon cache updated on install.
- Recover gracefully when the database file is missing.
- AdwSpinner template crash on libadwaita 1.5.
Breaking changes
- GTK 4.10+ and libadwaita 1.5+ required (1.6+ uses native
AdwButtonRow; 1.5 falls back to a backportedOtpButtonRow). - Configuration migrated to GSettings (CLI, GUI, search provider). GKeyFile settings are not migrated automatically — reconfigure on first run.
- libayatana-appindicator dependency removed in favour of native StatusNotifierItem.
Dependency floors
https://github.com/paolostivanin/OTPClient#requirements
Changes since rc1
- search-provider:
ActivateResultnow copies the OTP to the clipboard (was a no-op aside from the notification). - About dialog: credit Tobias Bernard as the icon artist (was listed as designer).
sha256: 472aa2a8443554ff6ca70f66fd1c1763f07fb9ae10a5a000e6cdf12dd1d3ac3b