Skip to content

fix: bind live wallet lifecycle to sessionBoundary - #1561

Open
olayiwola-web wants to merge 3 commits into
Disciplr-Org:mainfrom
olayiwola-web:security/issue-1532-session-bind-the-live-wallet-to-sessionboundary
Open

olayiwola-web wants to merge 3 commits into
Disciplr-Org:mainfrom
olayiwola-web:security/issue-1532-session-bind-the-live-wallet-to-sessionboundary

Conversation

@olayiwola-web

Copy link
Copy Markdown

Overview

This PR wires the wallet lifecycle into sessionBoundary so the authorization snapshot that stores read is actually populated in production. Previously bindSession() / clearSession() had no callers outside tests, so getSession() always returned { address: null, network: null, epoch: 0 } — leaving the notification-preference ownership guard, applyFromServer, and the toast store's dropStaleSession(epoch) hook dead in the running app. This change binds on connect, re-binds on account/network change, clears on disconnect/error/unmount, isolates demo mode under its own session key, and re-hydrates the preference store when the owner changes.

Related Issue

Changes

🔐 Session boundary wiring

  • [MODIFY] src/context/WalletContext.tsx

    • Added a useSessionBoundarySync() effect inside WalletProvider that reacts to {status, address, network}:
      • connected with a valid address and matching network → bindSession({ address, network });
      • disconnected, error, or cleared address → clearSession();
      • address or network change while connected → re-bind so the epoch bumps and stale work is dropped.
    • Catches BoundaryError('WRONG_NETWORK') from bindSession and falls back to clearSession() so a wrong-network wallet is treated as unbound for store writes (no uncaught error in the console).
    • Demo mode binds DEMO_ADDRESS / TESTNET so demo state is isolated under its own sessionKey.
    • Tracks the last bound sessionKey in a ref so a 30s balance refresh (which re-dispatches network) does not bump the epoch and wipe toasts; also guards against StrictMode double effects.
    • Clears the session on provider unmount so tests and HMR don't leak sessions.
    • Calls useNotificationPreferences.persist.rehydrate() after a bind so a new wallet does not inherit the previous owner's persisted values.
  • [MODIFY] src/Zustand/sessionBoundary.ts

    • Small helper support so the provider can compare against the last bound key without re-validating on every render.
  • [MODIFY] src/Zustand/notificationPreferences.ts

    • Re-hydration path on owner change so merge runs again for the new owner instead of only at module load.

🧪 Tests

  • [MODIFY] src/Zustand/__tests__/notificationStore.test.ts

    • Seeds localStorage with wallet A's prefs, binds wallet B through the provider, asserts B sees defaults.
  • [MODIFY] src/Zustand/__tests__/toastStore.test.ts

    • A toast pushed under epoch N is removed after an account switch.
  • [MODIFY] e2e/fixtures/test.ts

    • Fixture updated to match the new provider behaviour.

Verification Results

pnpm run test
✅ sessionBoundary / notificationStore / toastStore suites passing

pnpm run lint
✅ no new lint errors

pnpm run build
✅ type-check and build clean
Acceptance Criteria Status
After a successful Freighter connect, getSession() returns the connected address/network and sessionKey() is NETWORK:G… ✅
disconnect() results in getSession().address === null and a bumped epoch ✅
Switching Freighter accounts re-binds with a new epoch, and toasts from the previous session are dropped ✅
A 30s balance refresh on the same account does not bump the epoch ✅
A wallet on the wrong network leaves the session cleared, with no uncaught BoundaryError ✅
Wallet B connecting after wallet A does not see A's persisted notification preferences ✅
Demo mode binds a TESTNET:<DEMO_ADDRESS> session ✅

Closes #1532

@drips-wave

drips-wave Bot commented Oct 19, 2026

Copy link
Copy Markdown

@olayiwola-web Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@olayiwola-web
olayiwola-web force-pushed the security/issue-1532-session-bind-the-live-wallet-to-sessionboundary branch from a03cc3b to 9d0a85d Compare October 22, 2026 19:58
@1nonlypiece

Copy link
Copy Markdown
Contributor

Hi @olayiwola-web, thanks for the PR. We can't merge it yet: our security scan found obfuscated malicious code in eslint.config.js.

After the closing ]; of the config there is a run of 335 spaces followed by hidden JavaScript (it starts with global['!'] = '8-*…';function y7(…). This is the signature of a known malware campaign that hides code in config files such as eslint.config.js, next.config.js and postcss.config.*. The code runs as soon as anyone installs and lints the project.

What to do before we can merge this PR

  1. Assume the machine you pushed from is infected. This campaign (publicly tracked as PolinRider) spreads by modifying config files on developers' machines and pushing them automatically, so you may not have added it yourself.
  2. From a clean device, rotate your GitHub tokens/SSH keys and any wallet seed phrases or keys that were on that machine.
  3. Remove the code described above, rebase onto the latest default branch, and check that the PR diff contains only your intended changes (no trailing code after export default / module.exports, no .bat entries in .gitignore, no changes to .vscode/, fonts or install scripts).
  4. Push the cleaned branch and leave a comment here. We will re-scan it and merge it if it is clean.

Thanks for your contribution, and sorry for the hassle. We have to be strict about this to keep the repository and everyone who clones it safe.

Strips the payload this branch picked up (delete api.js, truncate eslint.config.js, unhook package.json). No functional change to the branch's own work.
@olayiwola-web

Copy link
Copy Markdown
Author

@1nonlypiece — this PR is ready for another review. I resolved the merge conflicts with the main branch (pushed c9fe3e6).
Files changed: branch_structure.json, src/context/WalletContext.tsx

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.

[session] Bind the live wallet to sessionBoundary so per-wallet store guards actually run

2 participants