diff --git a/apps/mobile-app/android/app/src/main/AndroidManifest.xml b/apps/mobile-app/android/app/src/main/AndroidManifest.xml
index 8d7910bac..98637ee82 100644
--- a/apps/mobile-app/android/app/src/main/AndroidManifest.xml
+++ b/apps/mobile-app/android/app/src/main/AndroidManifest.xml
@@ -12,7 +12,7 @@
-
+
diff --git a/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/credentialprovider/CredentialIdentityStore.kt b/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/credentialprovider/CredentialIdentityStore.kt
index bd975d55d..df694568f 100644
--- a/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/credentialprovider/CredentialIdentityStore.kt
+++ b/apps/mobile-app/android/app/src/main/java/net/aliasvault/app/credentialprovider/CredentialIdentityStore.kt
@@ -16,6 +16,12 @@ import org.json.JSONObject
* This class stores passkey metadata (rpId, userName, displayName, credentialId, userHandle)
* in SharedPreferences so that passkeys can be displayed without unlocking the vault.
*
+ * Security features:
+ * - Uses MODE_PRIVATE for app-private storage (no other apps can access)
+ * - Excluded from device backups (see backup_rules.xml and data_extraction_rules.xml)
+ * - Contains no passwords, only metadata (usernames, rpIds, displayNames)
+ * - Cleared when credential provider is disabled by user
+ *
* Similar to iOS ASCredentialIdentityStore, but using SharedPreferences as Android's
* Credential Manager API doesn't provide a system-level identity store.
*/
@@ -41,6 +47,8 @@ class CredentialIdentityStore private constructor(context: Context) {
}
}
+ // MODE_PRIVATE ensures only this app can access the data
+ // Backup exclusion rules prevent data from being backed up
private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
/**
@@ -154,6 +162,21 @@ class CredentialIdentityStore private constructor(context: Context) {
.apply()
}
+ /**
+ * Check if the credential identity store is empty.
+ * This is used to determine if initial sync is needed.
+ * @return true if the store has no credential identities
+ */
+ fun isStoreEmpty(): Boolean {
+ return try {
+ val jsonString = prefs.getString(KEY_PASSKEY_IDENTITIES, null)
+ jsonString == null || JSONArray(jsonString).length() == 0
+ } catch (e: Exception) {
+ Log.e(TAG, "Error checking if store is empty", e)
+ true // Assume empty if we can't read
+ }
+ }
+
/**
* Create a PasskeyIdentity from a Passkey and its parent Credential.
*/
diff --git a/apps/mobile-app/android/app/src/main/res/xml/backup_rules.xml b/apps/mobile-app/android/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 000000000..3b3bb8923
--- /dev/null
+++ b/apps/mobile-app/android/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/apps/mobile-app/android/app/src/main/res/xml/data_extraction_rules.xml b/apps/mobile-app/android/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 000000000..1fca8d9a5
--- /dev/null
+++ b/apps/mobile-app/android/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+