Skip to content

[WIP] fix(android): isolate storage instances per shared preferences name (fix: #1023)#1031

Draft
mateusz-pietras wants to merge 5 commits intojuliansteenbakker:developfrom
mateusz-pietras:fix/android-isolate-storage-instances-per-shared-preferences-name
Draft

[WIP] fix(android): isolate storage instances per shared preferences name (fix: #1023)#1031
mateusz-pietras wants to merge 5 commits intojuliansteenbakker:developfrom
mateusz-pietras:fix/android-isolate-storage-instances-per-shared-preferences-name

Conversation

@mateusz-pietras
Copy link

@mateusz-pietras mateusz-pietras commented Jan 13, 2026

Fix Android namespace isolation for sharedPreferencesName

Fixes #1023

Problem

Multiple FlutterSecureStorage instances with different sharedPreferences Name values shared a single cached FlutterSecureStorage instance, causing deleteAll() and other operations to affect data across namespaces.

Solution

Enforce namespace isolation at the plugin layer by routing each MethodChannel call to a dedicated FlutterSecureStorage instance per sharedPreferencesName. This ensures operations on namespace B never affect namespace A.

Implementation:

  • Plugin maintains a Map<String, FlutterSecureStorage> keyed by sharedPreferencesName
  • getOrCreateStorage() retrieves or creates the correct instance per namespace
  • All operations (read, write, delete, deleteAll, etc.) use the namespace-specific instance

Testing

✅ Added Android-only integration test that:

  • Writes the same key to two namespaces (namespace_a, namespace_b)
  • Calls deleteAll() on namespace_b
  • Asserts the key in namespace_a remains intact

Breaking Changes

None

Notes

  • SHARED_PREFERENCES_CONFIG_NAME remains global (stores migration/cipher metadata only). This poses a threat of possible encryption-related issues when two or more storages use different algorithms.
  • Actual stored values are fully isolated per sharedPreferencesName
  • Matches iOS/macOS behavior where each operation uses namespace-specific parameters

@mateusz-pietras mateusz-pietras changed the title fix(android): isolate storage instances per shared preferences name fix(android): isolate storage instances per shared preferences name (fix: #1023) Jan 13, 2026
@mateusz-pietras mateusz-pietras changed the title fix(android): isolate storage instances per shared preferences name (fix: #1023) [WIP] fix(android): isolate storage instances per shared preferences name (fix: #1023) Jan 13, 2026
@mateusz-pietras
Copy link
Author

Opened for initial code review as it directly solves the mentioned issue #1023, however, I will be working on introducing the same type of namespace/storage isolation for the SHARED_PREFERENCES_CONFIG_NAME. Meanwhile I would appreciate any suggestions regarding the presented code and the config isolation.

@mateusz-pietras
Copy link
Author

mateusz-pietras commented Jan 13, 2026

Important compatibility note (Android)

On some existing installs, upgrading to this fix can make previously-stored values “disappear” even when the app passes the same sharedPreferencesName.

Why this can happen

Issue #1023 is caused by the Android plugin reusing a single FlutterSecureStorage instance across namespaces and caching the first-initialized SharedPreferences. In practice, this could cause values written for namespace B to be physically persisted into namespace A’s SharedPreferences file (or the default FlutterSecureStorage prefs), depending on initialization order and app lifecycle.

After this PR, reads/writes are correctly bound to the requested namespace. If legacy data was accidentally stored under a different prefs file due to the bug, the upgraded version will not find it in the “correct” namespace anymore (because it never lived there).

In other words: this PR fixes the bug, but it also removes accidental cross-namespace behavior some apps may have relied on unknowingly. Apps that previously experienced namespace mixing might observe missing values after upgrade.

Suggested solution (introduce a new field; deprecate sharedPreferencesName)

To provide a clean, deterministic upgrade path while keeping strong isolation semantics, I suggest introducing a new Android option that becomes the canonical “namespace identifier”, and deprecating the existing sharedPreferencesName field.

Proposed API direction

  • New field (name TBD), e.g. storageNamespace / namespace / storageId
  • Deprecated field: sharedPreferencesName

Proposed behavior

  • The plugin will treat the new field as the source of truth for all Android namespacing:

    • The data SharedPreferences file name
    • Any config/marker preference stores used for algorithm/migration metadata
  • For backwards compatibility during the deprecation window:

    • If the new field is not provided, the plugin falls back to the deprecated sharedPreferencesName (current behavior).
    • If both are provided, the new field wins (and the old one is ignored, with a log/warn).

Migration expectations

This does not magically recover data that was physically written to the wrong prefs file due to the legacy bug (because that location was non-deterministic). However, introducing the new field provides:

  • a clearer contract going forward (“this is the stable namespace identifier”),
  • and a controlled breaking-change path where apps can move to the new field and avoid relying on low-level implementation details (SharedPreferences file naming).

Deprecation plan

  • Mark sharedPreferencesName as deprecated in docs (and API annotations where possible).
  • Document the upgrade path: “replace sharedPreferencesName with <newField>”.
  • Keep the fallback behavior for at least one major/minor cycle before removing sharedPreferencesName.

allow for secure storage config isolation per namespace, allowing usage of different algorythms between storages
@westracer
Copy link

SHARED_PREFERENCES_CONFIG_NAME remains global (stores migration/cipher metadata only). This poses a threat of possible encryption-related issues when two or more storages use different algorithms

This is actually a big problem that needs to be solved as well, and not just a "note". It's especially relevant since there's now a migration mechanism in v10 from AES/CBC to another cipher. If you have 2 storages with different algorithms/keys, they will conflict because they are using the same key alias in KeyStore, and all data will be lost as a result. Different keys should be used for different storages

@mateusz-pietras
Copy link
Author

SHARED_PREFERENCES_CONFIG_NAME remains global (stores migration/cipher metadata only). This poses a threat of possible encryption-related issues when two or more storages use different algorithms

This is actually a big problem that needs to be solved as well, and not just a "note". It's especially relevant since there's now a migration mechanism in v10 from AES/CBC to another cipher. If you have 2 storages with different algorithms/keys, they will conflict because they are using the same key alias in KeyStore, and all data will be lost as a result. Different keys should be used for different storages

Yes, that is true. I introduced a NamespacedConfigSource concept in the most recent commit, that allows to use separate algorithms per namespaced storage. That should solve the issue.

@westracer
Copy link

Yes, that is true. I introduced a NamespacedConfigSource concept in the most recent commit, that allows to use separate algorithms per namespaced storage. That should solve the issue.

That doesn't solve the issue fully because the key alias is the same for all algorithms/storages, which means the same key is used for all instances:

context.getPackageName() + ".FlutterSecureStoragePluginKey"

but obviously, keys should be separated as well for different algorithms/storages; otherwise, they will conflict with each other. Correct me if I'm wrong

@mateusz-pietras mateusz-pietras marked this pull request as draft January 29, 2026 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] Multiple instances with different sharedPreferencesName share cached preferences, causing cross-namespace data operations

2 participants