Files
IronStorage/docs/android/presentation-security.md
T
hugo a06f39af23
Android ARM verification / rust (push) Failing after 47m6s
Android ARM verification / android (push) Failing after 8s
Dependency security audit / rustsec (push) Successful in 23s
Enforce Android lifecycle and presentation security (#100)
2026-10-09 18:59:18 +02:00

7.4 KiB

Android presentation security

The Android client presents Rust-owned authentication state. crates/storage owns lease expiry, renewal, editor disposal, and clipboard content comparison. Kotlin forwards OS events and applies native window, input, and clipboard controls.

Lifecycle and immediate cleanup

Event Behavior
Foreground/resume Report foreground to Rust; remain locked until explicit unlock.
Explicit interaction Report user activity while unlocked; Rust decides lease renewal.
Pause/stop/task switch Immediately cover native content and clear protected presentation, then revoke the Rust lease.
Loss of top-resumed status Revoke for an inactive multi-window activity, except while its own OS biometric request is pending.
Native window focus lost/user leaves task Revoke even if Recents keeps the activity resumed; owned dialog/menu focus transfers remain usable.
Screen off/device locked Cancel authentication and revoke the lease.
Trim memory/low memory Clear protected presentation and revoke regardless of trim level.
Process recreation Start locked; never restore a lease or plaintext from saved state.
Manual lock/expiry Clear protected presentation, editors, OTP state, and active clipboard copies.

ProtectedPresentation invokes main-thread cleanup synchronously. Its callbacks clear setup secrets, cancel staged setup/remote operations, drop decrypted facade sections and editor/TOTP state, clear local dialog inputs, and cover the old native surface with an opaque overlay. Accessibility descendants are suppressed while covered. A generation change discards the old composition; the cleaned content is rendered before the resumed activity is uncovered. This keeps the frame clock running while preventing a stale frame from being visible between cleanup and recomposition. Rust revocation jobs run in order and complete before a subsequent unlock; stale results cannot republish a protected document. Owned dialogs and menus register their window before opening and report their own focus loss. The activity excludes those internal focus transfers and its active OS biometric request from external focus-loss revocation. The native cover stays in place until the cleaned root is both resumed and focused again.

TOTP ticks, recomposition, polling, animations, clipboard waiting/cleanup, and foreground notifications never report user activity. Rotation clears input and cancels setup operations rather than saving passphrases, armor, or tokens.

Windows, inputs, and diagnostics

The main activity sets FLAG_SECURE before Compose renders. Sensitive dialogs use secure windows; other dialogs inherit the activity flag. API 33+ also disables recent-task screenshots. Picture-in-picture is disabled in the manifest. An activity launched on a secondary display is revoked and finished. Task switching and inactive multi-window views clear protected presentation; returning does not automatically authenticate. Screen sharing/casting to nonsecure surfaces uses the same window policy. See Android's secure-window contract.

The activity and dialog roots exclude descendants from autofill and, on API 30+, content capture. Input sessions request no personalized learning, fullscreen IME, or extract UI. Password editors remain masked until an explicit reveal action. Unlocked accessible fields remain available to assistive technology; locked content is removed and covered. See the platform view controls and IME flags.

The production Android modules have no secret logging, analytics, notification payloads, or plaintext previews. Facade secret state and generated bridge records have redacted diagnostic strings; errors use typed Rust messages or generic OS failure text. Saved state contains navigation/setup choices, not protected input or decrypted documents. Tests inspect serialized saved bundles and immediate accessibility suppression. No memory-dump/export facility is provided.

The current manifest contains the main activity. Credential-provider components are delivered by issue #109; they must use these protected presentation/window controls when introduced. This issue does not install an empty credential service.

Clipboard ownership and limits

Each explicit copy obtains a separate native clipboard capability. Rust supplies the selected stored field and directs timeout/cancellation cleanup. Android marks the clip sensitive, assigns a random owner identity, and records the OS timestamp. Cleanup requires that identity, timestamp, and Rust's content comparison still match. Another copy with identical text, unrelated user text, or a restored old clip with a new timestamp is preserved. The adapter checks ownership again just before clearing. A successful write publishes explicit copy feedback. Revocation attempts cleanup synchronously while foreground clipboard access is still available; no previous clipboard history is retained or restored.

Android's ClipboardManager does not offer an atomic compare-and-clear operation across IPC calls. Another application can race the final check; this implementation cannot promise atomicity that the OS API lacks. Android 10+ can deny background clipboard reads, so cleanup cannot be guaranteed after the process has already lost access or died. System/OEM clipboard histories may retain copies independently. The sensitive flag suppresses supported clipboard previews; it is not encryption. See Android clipboard security and clipboard behavior.

FLAG_SECURE, autofill/content-capture exclusions, and IME requests rely on OS/OEM and keyboard cooperation. They cannot protect against a rooted OS, debugger, malicious keyboard, camera photographing the screen, or forensic memory access. JVM/Compose strings cannot be reliably zeroized; cleanup drops references and Rust zeroizes owned secret buffers, without claiming forensic JVM erasure.

Validation

Rust tests exercise lifecycle revocation/editor disposal, foreground without renewal, and immediate content-checked clipboard cleanup. Native facade tests use real encrypted entries and verify synchronous clearing for background, device lock, memory pressure, and process recreation. UI/OS tests cover native clipboard ownership and restored metadata, sensitive clips, secure windows, IME flags, saved bundles, rotation, and immediate accessibility masking.

The emulator matrix covers ARM64 API 26 (4 KB pages), API 37 (4 KB), and API 37 (strict 16 KB), including app/core/bridge instrumentation and five cold starts. The existing embedded API 37 emulator additionally exercises real Keystore biometric enrollment, unlock, cancellation, background cancellation, foreground remaining locked, and memory-pressure revocation using disposable public fixture credentials and its existing synthetic fingerprint. Computer use checks task switching/recents and cleared input on return. ARMv7 receives build/package validation; no suitable ARMv7 emulator image is available. Physical-device and OEM-specific capture/casting coverage are excluded from this milestone's test scope. Accessibility assertions do not claim spoken TalkBack verification.