Repository navigation
feat(security): Add persistent Duress PIN and fully usable decoy vault #61
Description
Summary / goal
Introduce an opt-in, persistent decoy ("public") vault with its own PIN, entered on the existing Encly lock screen. Users must be able to work normally in this vault: create, edit, organize, search, trash and restore notes. The real vault stays encrypted, intact and inaccessible during the decoy session.
This is different from the implemented Wipe PIN (#52): the Wipe PIN silently destroys the original vault; the Duress PIN must never erase it. The two features must coexist without ambiguity.
Non-goal: promising forensic-proof plausible deniability. A writable decoy can hide sensitive content from a casual onlooker, but is not guaranteed to hide the presence or history of the real vault from a forensic examiner.
User-visible flow
- Feature is disabled by default. Settings → Security → Duress PIN / Decoy Vault; creating, changing, disabling or resetting it requires fresh primary-vault authentication.
- User chooses a distinct 6-digit Duress PIN with the same PIN-strength policy as the primary PIN. Reject equal primary/Wipe/Duress PIN values; clearly handle later changes to any PIN.
- The ordinary lock keypad accepts primary PIN → real vault, Duress PIN → decoy vault, Wipe PIN (if configured) → its existing destructive behavior. No special button, vault selector, animation, confirmation, onboarding screen, toast or status text that identifies decoy unlock.
- First decoy unlock shows a normally functioning empty notes vault, not setup/onboarding. Users can create, save, edit, delete, search, tag, archive where supported, trash and restore notes; tasks should behave consistently with current vault capabilities.
- Decoy content persists after re-lock, process restart and device reboot. It is a genuine vault, not fake UI or transient dummy data.
- No decoy-facing page can expose primary note counts, titles, previews, search results, recents, tags, attachments, export history, backup metadata, security settings, or a "switch to real vault" control. Avoid decoy disclosure in Settings, Recents, notifications and Android task switcher.
- To change vault, lock completely and enter the destination PIN. No live switch with an unlocked database.
- Normal wrong-PIN backoff, lockout, system-device-lock, screen-off and auto-lock rules remain enforced; biometric/recovery semantics need explicit design and must not silently route a decoy session into the real vault.
- Removing decoy storage is optional and explicit, requires primary authentication and confirmation, destroys only decoy content, and does not impact the primary vault or its backups.
Architecture / threat model (design review is a prerequisite)
Inspect and integrate with the existing SecurityManager, SessionLockManager, SecureDatabaseManager, VaultStore, PIN/biometric/recovery key slots and SQLCipher/Room repositories. Preserve existing primary-vault key derivation, ENCLYVS3 compatibility, backup and onboarding behavior.
- True encryption isolation: generate a separate random DEK for the decoy, separate wrapped-key material, and an independently encrypted SQLCipher database (including journal/WAL/SHM handling). Never simulate a second vault using an
isDecoycolumn/UI filter in the same unlocked DB. - Explicit active-vault ownership: add a narrowly scoped session/vault abstraction; all DAOs, repositories, editor state, search caches and ViewModels must be scoped to the currently unlocked vault. On lock/switch, close DB, clear sensitive buffers on a best-effort basis, reset navigation and invalidate old handles. Prevent accidental cross-vault queries and delayed writes.
- PIN dispatch and observable behavior: document an actual strategy for distinct keys/slots and uniform unlock error handling. Avoid early-exit timing, animation, haptic or error differences that trivially disclose which PIN matched. Account for device-bound Keystore and wrong-attempt backoff. Do not invent custom cryptography.
- Wipe PIN coexistence: explicitly define precedence and collision behavior for 3 PINs; existing Wipe PIN Wipe PIN: a second PIN that silently erases the vault #52 must retain its semantics and never delete the primary vault when the Duress PIN is typed. Cover active wipe migration/recovery/old APK downgrade risks.
- Backups/recovery/biometrics: primary backups and recovery words must not be exposed or imported into decoy implicitly. Decide and document whether decoy backup/export, recovery and biometrics are supported; hide/restrict incompatible controls without signalling decoy state on the lock screen. No silent primary↔decoy merge.
- Persistent storage & forensic caveats: review file names, SQLite sidecars, preferences, storage size, filesystem timestamps, logs/crashes, Android backups, snapshots and storage residue. State clearly that metadata or full-device forensics may reveal multiple vaults. Flash secure-erasure and convincing deniability cannot be guaranteed.
- Lifecycle safety: app kill during creation, PIN change, decoy deletion or startup recovery must not orphan the primary vault. Failed decoy unlock/DB corruption must never fall back into the primary vault. Screen-off, task-switching, notifications/deep links and screenshots respect existing secrecy guarantees.
- Migration: no mandatory setup or re-wrap for existing users; opt-in migration must be crash-safe, and upgrade/downgrade compatibility must be explicitly verified or a documented downgrade restriction set.
Proposed implementation phases
- Design/security RFC: key hierarchy, database switching, 3-PIN state machine, forensic limits, backup/restore behavior, migration and threat-model updates.
- Core isolation: separate decoy key/SQLCipher lifecycle; vault-aware repositories and lock manager; process-death handling.
- Configuration & UX: secure enable/change/remove flow, real/decoy unlock from the same screen, functioning notes/tasks with normal design.
- Hardening: wipe-PIN interaction, deep links, backups, memory/cache, metadata exposure, lifecycle regressions, privacy wording.
- Documentation & localization:
SECURITY.md,docs/architecture.md,PRIVACY.md, CHANGELOG and all supported locales.
Acceptance criteria / verification
- The two PINs unlock two different, persistent encrypted databases, and a third Wipe PIN (when set) remains independently destructive.
- Edits in decoy never appear in real vault, and vice versa; database keys and plaintext cannot cross between sessions.
- Primary vault remains intact through decoy creation, usage, relock, restart, deletion, PIN changes and failed imports/exports.
- UI and first frame on resume show no content from the previous vault; no decoy label is visible during decoy use.
- No regressions in current session-lock, retry/backoff, biometrics, recovery, encrypted backups or wipe-PIN semantics.
- Automated state-machine/integration tests: 3 PINs and collisions, wrong attempts, two-way switching, concurrent async work, process kill at each setup stage, database lock/sidecars, OS lock, upgrade and documented downgrade behavior.
- Security review signs off realistic threats and limitations. Do not advertise guaranteed forensic deniability.
Related work
- Wipe PIN: a second PIN that silently erases the vault #52: Wipe PIN, not decoy vault.
- [Beta blocker][Security] Auto-lock and close the encrypted DB when Encly leaves foreground #9: Auto-lock and encrypted DB closing.
- [Security epic] Cryptographic architecture hardening before beta #17: Security architecture hardening.
Activity
feat(security): add duress PIN with a separate decoy vault
pasichDev commentedon Oct 14, 2026
Implemented in #65 (426af53): duress PIN with a separate, persistent decoy vault (own DEK and SQLCipher database), uniform three-slot PIN dispatch, and a decoy session that cannot reach the real vault's keys, backups or settings. Limits are documented in SECURITY.md → Duress PIN. Ships with the next release.
pasichDev commentedon Oct 14, 2026
Checklist updated against 426af53. Still open:
fix(security): bound PIN guesses around the duress PIN and hide the d…
fix(import): keep media out of the limits and fix review findings