Skip to content

fix: Fix StorageService Key Encoding cp-7.66.0#26268

Merged
andrepimenta merged 8 commits into
mainfrom
fix/storage-service-key-encoding
Feb 20, 2026
Merged

fix: Fix StorageService Key Encoding cp-7.66.0#26268
andrepimenta merged 8 commits into
mainfrom
fix/storage-service-key-encoding

Conversation

@andrepimenta

@andrepimenta andrepimenta commented Feb 19, 2026

Copy link
Copy Markdown
Member

Description

Problem

The redux-persist-filesystem-storage library has two problematic behaviors when handling arbitrary keys in StorageService:

  1. Slashes (/) create subdirectories: Keys containing slashes are stored in subdirectories, making them unreachable via getAllKeys. This breaks the clear method for affected keys.

  2. Hyphens (-) get corrupted: The library's internal fromFileName function converts hyphens to colons (:), meaning keys like simple-key are returned as simple:key by getAllKeys. This causes a permanent mismatch between stored keys and returned keys.

Solution

This PR introduces URI-style encoding for problematic characters in both namespace and key portions of storage keys:

  • /%2F (prevents subdirectory creation)
  • -%2D (prevents hyphen-to-colon corruption)
  • %%25 (prevents double-encoding issues)

The encoding is applied via shared utility functions (encodeStorageKey/decodeStorageKey) used by both:

  • mobileStorageAdapter in storage-service-init.ts
  • Migration 118 which stores snap source code

Backward Compatibility

This change is backward compatible and will NOT break existing production keys.

  • Existing keys like storageService:TokenListController:tokensChainsCache:0x1 are unaffected because:
    • The namespace (TokenListController) has no special characters → encoding produces identical output
    • Colons (:) are not encoded - they pass through unchanged
    • The key portion (tokensChainsCache:0x1) contains no hyphens or slashes
  • Strings without -, /, or % characters produce identical output when encoded
  • This means all current production keys work exactly as before, while future keys with special characters will be handled correctly

Examples

# No special characters → unchanged (backward compatible)
storageService:TokenListController:tokensChainsCache:0x1 → storageService:TokenListController:tokensChainsCache:0x1

# Snap ID with slashes and hyphens → encoded
storageService:SnapController:npm:@metamask/bip32-keyring-snap → storageService:SnapController:npm:@metamask%2Fbip32%2Dkeyring%2Dsnap

# Namespace with hyphen → encoded
storageService:Some-Controller:some-key → storageService:Some%2DController:some%2Dkey

# Key with slashes → encoded (prevents subdirectory creation)
storageService:TestController:nested/path/key → storageService:TestController:nested%2Fpath%2Fkey

Files Changed

File Change
app/core/Engine/utils/storage-service-utils.ts New utility with encodeStorageKey/decodeStorageKey functions
app/core/Engine/utils/storage-service-utils.test.ts 35 unit tests for the encoding utilities
app/core/Engine/controllers/storage-service-init.ts Apply encoding in mobileStorageAdapter methods
app/core/Engine/controllers/storage-service-init.test.ts 22 new tests for key and namespace encoding behavior
app/store/migrations/119.ts Encode snap IDs when storing snap source code

Changelog

CHANGELOG entry: null

Related issues

Refs: StorageService key handling issues with redux-persist-filesystem-storage

Manual testing steps

Feature: StorageService key encoding

  Scenario: Keys with hyphens are stored and retrieved correctly
    Given the app is running

    When StorageService stores a key containing hyphens (e.g., "npm:@metamask/bip32-keyring-snap")
    Then the key is encoded as "npm:@metamask%2Fbip32%2Dkeyring%2Dsnap" on disk
    And getAllKeys returns the original key "npm:@metamask/bip32-keyring-snap"
    And getItem with the original key returns the stored data

  Scenario: Keys with slashes are stored and retrieved correctly
    Given the app is running

    When StorageService stores a key containing slashes (e.g., "nested/path/key")
    Then the key is stored as a single file (not in subdirectories)
    And getAllKeys returns the original key "nested/path/key"
    And clear removes the key correctly

  Scenario: Existing keys with colons remain unchanged
    Given existing production keys like "storageService:TokenListController:tokensChainsCache:0x1"

    When the app starts with this fix
    Then the existing keys are still accessible
    And no migration is required for existing data

Screenshots/Recordings

N/A - No UI changes

Before

N/A

After

N/A

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Medium Risk
Changes the on-disk key format for any namespaces/keys containing %, /, or -, which could impact access to previously stored data for those cases; behavior is covered by new unit tests but touches persistence and migration logic.

Overview
Fixes StorageService filesystem key handling by URI-encoding namespaces and keys (encoding %, /, - while leaving : intact) before calling redux-persist-filesystem-storage, and decoding keys returned from getAllKeys.

Updates mobileStorageAdapter to apply this encoding in getItem/setItem/removeItem, use encoded namespace prefixes in getAllKeys/clear, and adds a shared storage-service-utils module with thorough unit coverage. Migration 119 is updated (and its test adjusted) to store snap source code under the encoded snap ID so snap IDs containing slashes/hyphens are retrievable/clearable.

Written by Cursor Bugbot for commit 59b39f4. This will update automatically on new commits. Configure here.

andrepimenta and others added 2 commits February 19, 2026 12:32
…orage quirks

The redux-persist-filesystem-storage library has two problematic behaviors:
1. Slashes (/) in keys create subdirectories, making keys unreachable via getAllKeys
2. Hyphens (-) get converted to colons (:) by fromFileName, corrupting the key

This fix introduces URI-style encoding for these characters:
- `/` → `%2F`
- `-` → `%2D`
- `%` → `%25` (to prevent double-encoding)

The encoding is applied only to the key portion, not the namespace,
to maintain backward compatibility with existing production keys.

Co-authored-by: Cursor <cursoragent@cursor.com>
Extends the key encoding to also encode the namespace portion for
future-proofing. Since current namespaces (e.g., TokenListController)
have no special characters, this produces identical output and
maintains backward compatibility with existing production keys.

Co-authored-by: Cursor <cursoragent@cursor.com>
@andrepimenta andrepimenta requested a review from a team as a code owner February 19, 2026 12:45
@andrepimenta andrepimenta added the team-mobile-platform Mobile Platform team label Feb 19, 2026
andrepimenta and others added 2 commits February 19, 2026 12:47
Apply encodeStorageKey to snap IDs when storing snap source code
in the filesystem. This ensures snap IDs with special characters
(e.g., npm:@metamask/bip32-keyring-snap) are stored correctly.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread app/core/Engine/controllers/storage-service-init.ts
Comment thread app/core/Engine/utils/storage-service-utils.ts Outdated
Comment thread app/core/Engine/controllers/storage-service-init.ts
Comment thread app/core/Engine/utils/storage-service-utils.ts Outdated
Comment thread app/core/Engine/utils/storage-service-utils.ts Outdated
*/
export const encodeStorageKey = (key: string): string =>
key
.replace(/%/g, '%25') // Encode % first to avoid double-encoding

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are URI encoding anyway we could consider using encodeURIComponent?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't encode -

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but what about other characters than / and -?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It encodes : as well I believe? So it would not be backwards compatible

@FrederikBolding FrederikBolding Feb 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. I guess the worry is whether there are characters we haven't considered here that would be problematic in the future?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. I think only - because of the conversion, and / because it was creating folders. But I will create a ticket for us to do a bit more digging after but we could merge this for now to unblock the release if you are ok with it?

Comment thread app/core/Engine/utils/storage-service-utils.ts
Co-authored-by: Cursor <cursoragent@cursor.com>
const fullKey = `${STORAGE_KEY_PREFIX}SnapController:${snap.id}`;
// Encode the snap ID to handle special characters (e.g., slashes and hyphens in npm:@metamask/bip32-keyring-snap)
const encodedSnapId = encodeStorageKey(snap.id as string);
const fullKey = `${STORAGE_KEY_PREFIX}SnapController:${encodedSnapId}`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modifying shipped migration breaks existing snap data access

High Severity

Migration 119 is a pre-existing migration being modified to add encodeStorageKey for snap IDs. If any users have already run the original migration, their snap source codes are stored under unencoded keys (e.g., with literal / and -). Since migrations only run once, those keys won't be re-encoded, but the storage adapter now exclusively uses encoded keys (%2F, %2D). This makes previously stored snap source codes permanently inaccessible via getItem. A separate new migration to re-encode existing keys is needed instead of modifying the existing one.

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not in production

Explain why we encode `-` and `/` but not `:` - existing production
keys use colons, so encoding them would break backward compatibility.

Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Comment thread app/store/migrations/119.ts
Comment thread app/core/Engine/controllers/storage-service-init.ts
andrepimenta and others added 2 commits February 19, 2026 14:49
Update assertions to expect encoded keys (hyphens as %2D) since
the migration now uses encodeStorageKey for snap IDs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Re-add the production log statement that was accidentally removed
when cleaning up debug console.logs.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: FlaskBuildTests
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: medium
  • AI Confidence: 85%
click to see 🤖 AI reasoning details

E2E Test Selection:
This PR introduces storage key encoding/decoding utilities for the StorageService to handle special characters (hyphens, slashes, percent signs) in storage keys used by redux-persist-filesystem-storage.

Key Changes:

  1. New utility functions encodeStorageKey and decodeStorageKey in storage-service-utils.ts
  2. Updated storage-service-init.ts to use encoding for all storage operations
  3. Migration 119 updated to encode snap IDs when storing snap source code

Impact Analysis:

  • StorageService is used by SnapController (for snap source code) and TokenListController (for token chain cache)
  • The encoding specifically targets hyphens (-), slashes (/), and percent signs (%) - characters commonly found in snap IDs like npm:@metamask/bip32-keyring-snap
  • TokenListController keys (like tokensChainsCache:0x1) don't contain these special characters, so they're unaffected
  • Colons are explicitly NOT encoded to maintain backward compatibility

Why FlaskBuildTests:

  • Snaps functionality is the primary consumer of this change
  • Snap IDs commonly contain slashes and hyphens that this encoding addresses
  • The migration specifically handles snap source code storage with the new encoding
  • FlaskBuildTests covers snap lifecycle, installation, and functionality which would exercise the StorageService

Why not other tags:

  • TokenListController uses simpler keys without hyphens/slashes
  • No direct impact on confirmations, accounts, network, trading, or other wallet features
  • This is infrastructure-level storage handling, not user-facing functionality changes

Performance Test Selection:
This change affects storage key encoding/decoding for the StorageService, which is an infrastructure-level change for data persistence. The encoding/decoding operations are simple string replacements that have negligible performance impact. The change doesn't affect UI rendering, data loading patterns, app startup, or any critical user flows that would warrant performance testing. The StorageService operations are already asynchronous and the encoding adds minimal overhead.

View GitHub Actions results

@FrederikBolding FrederikBolding changed the title fix: Fix StorageService Key Encoding fix: Fix StorageService Key Encoding cp-7.66.0 Feb 19, 2026
@andrepimenta andrepimenta added the release-blocker This bug is blocking the next release label Feb 19, 2026
@sonarqubecloud

Copy link
Copy Markdown

@andrepimenta andrepimenta added this pull request to the merge queue Feb 20, 2026
Merged via the queue into main with commit 5d11a21 Feb 20, 2026
234 of 245 checks passed
@andrepimenta andrepimenta deleted the fix/storage-service-key-encoding branch February 20, 2026 11:35
@github-actions github-actions Bot locked and limited conversation to collaborators Feb 20, 2026
@metamaskbot metamaskbot added the release-7.68.0 Issue or pull request that will be included in release 7.68.0 label Feb 20, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.68.0 Issue or pull request that will be included in release 7.68.0 release-blocker This bug is blocking the next release size-L team-mobile-platform Mobile Platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants