Skip to content

fix(xbull): always close the bridge so rejected flows don't leak window listeners - #110

Open
mihaieremia wants to merge 1 commit into
Creit-Tech:mainfrom
mihaieremia:fix/xbull-bridge-listener-leak
Open

fix(xbull): always close the bridge so rejected flows don't leak window listeners#110
mihaieremia wants to merge 1 commit into
Creit-Tech:mainfrom
mihaieremia:fix/xbull-bridge-listener-leak

Conversation

@mihaieremia

Copy link
Copy Markdown

The bug

Every xBullWalletConnect instance registers a permanent window message listener and generates a fresh nacl keypair in its constructor. In xbull.module.ts, bridge.closeConnections() is only reached on the success path — when connect() / sign() / signMessage() rejects (most commonly because the user closes the xBull popup without approving), the catch rethrows and the listener leaks for the rest of the browser session.

On the next xBull action, a new bridge is created and the wallet's XBULL_INITIAL_RESPONSE is encrypted for the new bridge's public key. The leaked listener also receives the message, nacl.box.open with its stale secret key returns null, and @creit.tech/xbull-wallet-connect throws:

Error: Decrypted message is null
    at P.decryptFromReceiver (...)

…inside an RxJS subscriber, so it surfaces as an unhandled error in the host application. We see these regularly in production error reports at xoxno.com from users retrying an xBull connect/sign after dismissing the popup.

The fix

Move closeConnections() into a finally in all three flows (getAddress, signTransaction, signMessage), so the listener and the popup-poll timer are torn down whether the flow resolves or rejects. closeConnections() completes the bridge's subjects and removes the message listener, so calling it on the error path is exactly its intended use.

No behavior change on the success path — the same call just moves from the try body into finally.

We're currently shipping this as a patch-package patch on @creit.tech/stellar-wallets-kit@2.3.0 and would love to drop it in favor of an upstream release.

…ow listeners

Every xBullWalletConnect instance registers a permanent window 'message'
listener and a fresh nacl keypair. closeConnections() only ran on the
success path, so a rejected connect/sign/signMessage (e.g. the user
closes the popup) leaked the listener for the rest of the session. The
next action's XBULL_INITIAL_RESPONSE is encrypted for the new bridge's
keypair; the leaked listener's box.open then fails and the bridge throws
'Decrypted message is null' as an unhandled error inside its RxJS
subscriber. Move closeConnections() into a finally in all three flows.
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.

1 participant