Skip to content

feat(security): Add persistent Duress PIN and fully usable decoy vault #61

Closed

Description

@pasichDev

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.

  1. 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 isDecoy column/UI filter in the same unlocked DB.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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

Activity

  1. pasichDev commented on Oct 14, 2026

    @pasichDev
    OwnerAuthor

    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.

  2. pasichDev commented on Oct 14, 2026

    @pasichDev
    OwnerAuthor

    Checklist updated against 426af53. Still open:

    • PRIVACY.md does not mention the decoy vault yet (SECURITY.md, architecture, CHANGELOG and all locales are done).
    • Not yet automated: concurrent async work during a switch, a process kill at each setup stage, OS lock during unlock, and upgrade/downgrade on a device; the instrumented SQLCipher tests were compiled but not run on an emulator.
    • No independent security review sign-off yet.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions