Implement Android app-private paths, configuration, cache, and backup policy in the Rust core #90

Closed
opened 2026-08-17 19:10:19 +00:00 by hugo · 1 comment
Owner

Goal

Make all storage-owned files work correctly inside Android's sandbox and survive safe application updates without leaking into device or cloud backups.

Scope

  • Pass canonical Android app-private files, cache, and no-backup directories into the Rust runtime at initialization; do not infer Android paths from HOME, current working directory, or public storage.
  • Keep the password-store clone, GPG key material, config.toml, TOTP discovery catalog, and transactional scratch space under storage-owned capability roots with atomic mutation behavior.
  • Choose files versus no-backup placement per threat and recovery model. Exclude the repository, key material, secure-store ciphertext, token/passphrase artifacts, TOTP catalog, and temporary plaintext from Auto Backup/device transfer unless a later audited encrypted export feature explicitly permits it.
  • Preserve the repository, configuration, credentials, and TOTP cache across normal APK/Play updates. Do not treat update as uninstall/reset.
  • Handle low-storage, read-only, missing, corrupt, restored-without-Keystore, interrupted atomic write, and process-death cases with typed recovery.
  • Set the strongest meaningful Unix-style permissions on Android while relying on the application sandbox rather than claiming unsupported filesystem guarantees.
  • Ensure cache and temporary cleanup never traverses or mutates the password-store repository outside Rust-owned capabilities.
  • Add migration/version hooks for future Android storage layout changes.

Libraries and APIs

Android Context.filesDir, cacheDir, noBackupFilesDir, data-extraction/backup rules, and storage-owned Rust filesystem/capability APIs. No Storage Access Framework access is needed for the live repository.

Acceptance criteria

  • Fresh install, app update, process recreation, storage pressure, and corrupt cache fixtures have deterministic outcomes.
  • Normal updates retain the local repository and valid TOTP cache; uninstall behavior is documented.
  • Backup inspection shows no password repository, GPG key, secret-store payload, token, passphrase, TOTP metadata, or plaintext temporary file.
  • Kotlin never lists or mutates repository contents.
  • Android path behavior has Rust unit tests and instrumented sandbox tests.

Depends on #88, the Rust/UniFFI packaging issue, and the platform-neutral contract issue. Part of #87.

Android support update (2026-10-08)

User-directed scope: Android release and CI targets are arm64-v8a and armeabi-v7a. x86_64 Android is excluded for now. Validation for every Android milestone issue uses emulator devices only; physical-device testing and physical-device checklists are excluded. Record the limits of available ARM emulator/API images without claiming physical-device coverage.

## Goal Make all storage-owned files work correctly inside Android's sandbox and survive safe application updates without leaking into device or cloud backups. ## Scope - Pass canonical Android app-private files, cache, and no-backup directories into the Rust runtime at initialization; do not infer Android paths from `HOME`, current working directory, or public storage. - Keep the password-store clone, GPG key material, `config.toml`, TOTP discovery catalog, and transactional scratch space under storage-owned capability roots with atomic mutation behavior. - Choose files versus no-backup placement per threat and recovery model. Exclude the repository, key material, secure-store ciphertext, token/passphrase artifacts, TOTP catalog, and temporary plaintext from Auto Backup/device transfer unless a later audited encrypted export feature explicitly permits it. - Preserve the repository, configuration, credentials, and TOTP cache across normal APK/Play updates. Do not treat update as uninstall/reset. - Handle low-storage, read-only, missing, corrupt, restored-without-Keystore, interrupted atomic write, and process-death cases with typed recovery. - Set the strongest meaningful Unix-style permissions on Android while relying on the application sandbox rather than claiming unsupported filesystem guarantees. - Ensure cache and temporary cleanup never traverses or mutates the password-store repository outside Rust-owned capabilities. - Add migration/version hooks for future Android storage layout changes. ## Libraries and APIs Android `Context.filesDir`, `cacheDir`, `noBackupFilesDir`, data-extraction/backup rules, and storage-owned Rust filesystem/capability APIs. No Storage Access Framework access is needed for the live repository. ## Acceptance criteria - Fresh install, app update, process recreation, storage pressure, and corrupt cache fixtures have deterministic outcomes. - Normal updates retain the local repository and valid TOTP cache; uninstall behavior is documented. - Backup inspection shows no password repository, GPG key, secret-store payload, token, passphrase, TOTP metadata, or plaintext temporary file. - Kotlin never lists or mutates repository contents. - Android path behavior has Rust unit tests and instrumented sandbox tests. Depends on #88, the Rust/UniFFI packaging issue, and the platform-neutral contract issue. Part of #87. ## Android support update (2026-10-08) User-directed scope: Android release and CI targets are **arm64-v8a** and **armeabi-v7a**. x86_64 Android is excluded for now. Validation for every Android milestone issue uses emulator devices only; physical-device testing and physical-device checklists are excluded. Record the limits of available ARM emulator/API images without claiming physical-device coverage.
hugo added this to the 06 - Implement an Android version of the software milestone 2026-08-17 19:10:19 +00:00
hugo added the enhancement label 2026-08-17 19:10:19 +00:00
Author
Owner

Implemented in 874c813.

  • Rust now initializes from the three canonical Android Context roots. Repository, keys, configuration, TOTP catalog, version/device binding, and scratch live under no-backup capabilities; disposable cache has a separate capability. Existing default Rust APIs use the explicit process context and Android never infers HOME/cwd/public paths.
  • Added atomic versioned layout, 0700/0600 permissions, bounded/corrupt catalog recovery, safe orphan cleanup, live-owner locks and creation gates, non-destructive configuration/interrupted-setup recovery, and typed filesystem/secure-binding errors. Missing layout metadata cannot silently discard a prior binding. Device-binding verification executes through the existing platform callback; #93 owns the real Android Keystore adapter.
  • Fixed the Android sandbox incompatibilities discovered by native testing: safe rustix file locks for the pinned std, private named atomic files instead of forbidden anonymous /proc linking, and atomic no-overwrite rename instead of forbidden configuration hard links. Shared callers retain their existing behavior on other platforms.
  • Kotlin supplies only OS directory facts, invokes Rust off the UI thread, and presents Rust state/recovery. It does not list/read/write repository contents. Manifest backup remains disabled; installed legacy/cloud/device-transfer XML excludes all nine protected domains. Update/uninstall and recovery policy is documented in docs/android/private-storage.md.

Verification: all four required workspace gates passed (431 Rust tests). Android ktlint, unit tests, lint, and debug/release APK+AAB audits passed for all three ABIs. Each persistent API 37 emulator (4 KB and strict 16 KB) passed eight native tests plus two app tests covering 25 shell state/tab combinations and installed backup policy. A fresh native sandbox created a real protected GPG key and Git repository, saved preferences, and retained that fixture through later test/APK updates. Autonomous ADB UI checks covered all five tabs, process recreation, debug/release install-over updates, native release startup and preserved layout. Rust fixtures verify encrypted pass data and valid TOTP cache across updates/relocation, corrupt cache, interrupted writes/setup, lock ownership, link-safe cleanup, version changes and device-binding loss/mismatch.

Swift native private-storage/redaction validation and 588 shared phone/Watch contract entries passed. xcodegen and affected iPhone/Watch/AutoFill simulator builds passed; the persistent iPhone app launched with its existing store intact. No simulator was erased or app uninstalled. Testing uses emulators only, as requested.

Final review matched every #90 scope/acceptance item to implementation and evidence. Backup inspection was read-only: automatic approval review rejected an active bmgr backupnow attempt because its transport could export sensitive data. Installed flags/XML and no-backup placement were verified instead; no backup payload was exported. App update and process-state preservation were checked independently of build success.

Implemented in [874c813](https://git.rfc1437.de/hugo/IronStorage/commit/874c8139a4add6aba780c410c92e257fbbde8da6). - Rust now initializes from the three canonical Android Context roots. Repository, keys, configuration, TOTP catalog, version/device binding, and scratch live under no-backup capabilities; disposable cache has a separate capability. Existing default Rust APIs use the explicit process context and Android never infers HOME/cwd/public paths. - Added atomic versioned layout, 0700/0600 permissions, bounded/corrupt catalog recovery, safe orphan cleanup, live-owner locks and creation gates, non-destructive configuration/interrupted-setup recovery, and typed filesystem/secure-binding errors. Missing layout metadata cannot silently discard a prior binding. Device-binding verification executes through the existing platform callback; #93 owns the real Android Keystore adapter. - Fixed the Android sandbox incompatibilities discovered by native testing: safe rustix file locks for the pinned std, private named atomic files instead of forbidden anonymous /proc linking, and atomic no-overwrite rename instead of forbidden configuration hard links. Shared callers retain their existing behavior on other platforms. - Kotlin supplies only OS directory facts, invokes Rust off the UI thread, and presents Rust state/recovery. It does not list/read/write repository contents. Manifest backup remains disabled; installed legacy/cloud/device-transfer XML excludes all nine protected domains. Update/uninstall and recovery policy is documented in docs/android/private-storage.md. Verification: all four required workspace gates passed (431 Rust tests). Android ktlint, unit tests, lint, and debug/release APK+AAB audits passed for all three ABIs. Each persistent API 37 emulator (4 KB and strict 16 KB) passed eight native tests plus two app tests covering 25 shell state/tab combinations and installed backup policy. A fresh native sandbox created a real protected GPG key and Git repository, saved preferences, and retained that fixture through later test/APK updates. Autonomous ADB UI checks covered all five tabs, process recreation, debug/release install-over updates, native release startup and preserved layout. Rust fixtures verify encrypted pass data and valid TOTP cache across updates/relocation, corrupt cache, interrupted writes/setup, lock ownership, link-safe cleanup, version changes and device-binding loss/mismatch. Swift native private-storage/redaction validation and 588 shared phone/Watch contract entries passed. xcodegen and affected iPhone/Watch/AutoFill simulator builds passed; the persistent iPhone app launched with its existing store intact. No simulator was erased or app uninstalled. Testing uses emulators only, as requested. Final review matched every #90 scope/acceptance item to implementation and evidence. Backup inspection was read-only: automatic approval review rejected an active bmgr backupnow attempt because its transport could export sensitive data. Installed flags/XML and no-backup placement were verified instead; no backup payload was exported. App update and process-state preservation were checked independently of build success.
hugo closed this issue 2026-10-08 12:37:25 +00:00
Sign in to join this conversation.