Skip to content

fix: harden the wallet store against tampered and polluted state - #132

Merged
nulllpc merged 4 commits into
mainfrom
fix/wallet-store-hardening
Oct 23, 2026
Merged

nulllpc merged 4 commits into
mainfrom
fix/wallet-store-hardening

Conversation

@quocle108

@quocle108 quocle108 commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Description

  • Receive addresses are no longer persisted; they are re-derived from the worklet on every launch.
  • The encryption key is stored device-only, like the seed and entropy.
  • Store updates reject __proto__, constructor and prototype as wallet id or network name.

Motivation and Context

Persisted addresses could be swapped on disk, the encryption key was eligible for Keychain backup while the data it protects was not, and a crafted key could pollute Object.prototype.

Related Issue

Closes #

Type of change

  • Bug fix (non-breaking change which fixes an issue)

@quocle108
quocle108 requested a review from nulllpc October 20, 2026 09:21
@quocle108 quocle108 self-assigned this Oct 20, 2026
Comment thread src/utils/schemas.ts Outdated
/**
* Property names that would reach Object.prototype when used as a key of a plain object
*/
export const RESERVED_OBJECT_KEYS: ReadonlySet<string> = new Set(['__proto__', 'constructor', 'prototype'])

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.

RESERVED_OBJECT_KEYS only blocks proto, constructor and prototype, but ??= reads through to any inherited property

I think we can replace the set with a predicate so there is no list to maintain:

export const isReservedObjectKey = (key: string): boolean =>
key in Object.prototype || key === 'prototype'

To be clear, this check will only apply to write attempts from now on, and has no effect on past/created wallets right?

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.

Dropped RESERVED_OBJECT_KEYS and replaced by isReservedObjectKey

Not only writes, it apply to read too. resolveWalletId is used by the balance and address getters, and the secure-storage read helper validates the identifier as well.

No effect on existing wallets. I loaded a stored blob with a normal wallet plus reserved keys: the app starts, the normal wallet reads fine

@nulllpc nulllpc Oct 23, 2026 •

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.

The one caveat is that a wallet already stored under a reserved id (for example constructor) can no longer be read or deleted after this change. In practice, I think this is acceptable since these IDs are unlikely, such an ID would likely corrupt the store before this PR so it's an edge case

Comment thread src/store/walletStore.ts
return (state: WalletState | undefined) => {
if (state) {
log('🔄 Rehydrating wallet state - resetting loading states')
state.addresses = {}

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.

We need to ensure this doesn't alter the behavior for consumer. Can you test against wdk-examples? What I expect is that all functions behave the same way, it's just a bit slower without the wallet store, but this is only addresses so fetching from the worklet shouldn't be too taxing

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.

Tested with wdk-examples. Same results after loading. The only difference is one getAddress call per network on first load, then it's cached, and data is undefined on the first render instead of coming from storage

@nulllpc

nulllpc commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Also, this PR addresses 3 different points. Should we make issues to track these points?

@nulllpc
nulllpc merged commit 9548163 into main Oct 23, 2026
7 checks passed
@nulllpc
nulllpc deleted the fix/wallet-store-hardening branch October 23, 2026 10:49
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.

2 participants