fix(vault): restore this-binding in guardVaultAccess for privileged-capable methods - #144
Conversation
…apable methods
guardVaultAccess wraps every PRIVILEGED_CAPABLE method (getPublicKey,
encrypt, decrypt, createHmac, verifyHmac, createSignature, verifySignature,
revealCounterpartyKeyLinkage, revealSpecificKeyLinkage, acquireCertificate,
proveCertificate, listCertificates) in a privilege check before calling
the real wallet method. The pass-through call was a bare invocation --
`value(args, originator)` -- which drops `this`. The sibling branch two
lines above, for methods NOT in PRIVILEGED_CAPABLE, already does this
correctly via `value.bind(target)`; this branch never got the same
treatment.
Real wallet implementations (e.g. @bsv/wallet-toolbox-mobile's
SimpleWalletManager) read `this` internally:
async getPublicKey(args, originator) {
this.ensureCanCall(originator);
return await this.underlying.getPublicKey(args, originator);
}
With `this` unbound, `this.ensureCanCall` resolves to undefined and
calling it throws "undefined is not a function" -- with no property name
in the message, since it's a computed/member call inside a dynamically
evaluated WebView script. This broke every ordinary (non-privileged)
getPublicKey call from the in-tab CWI bridge, not just the privileged ones
the guard was meant to block -- and getPublicKey is the first call a
BRC-100 connect makes, so this silently broke the browser's own wallet
connect flow for any website opened in-app, both via the injected
window.CWI substrate and (same guardVaultAccess call site) the
desktop-pairing WalletClient.
The existing test suite didn't catch this because fakeWallet()'s stub
methods are plain closures that ignore `this` entirely -- they can't
distinguish a bound call from an unbound one. Added a regression test
using a real class instance method that reads `this`, confirmed it fails
against the unfixed code (this.ensureCanCall equivalent resolves to
undefined) and passes against the fix. All 16 existing + new tests green,
tsc clean (pre-existing, unrelated errors in __tests__/manual/ aside).
c883a53 to
b8613ba
Compare
|
I reviewed this PR. I think this is a correct and important fix, and I do not see a blocker. The bug is real: value(args, originator)That loses the wallet instance as The fix: value.call(target, args, originator)is the right shape. It preserves the existing privilege gate, still rejects non-admin The added regression test is also directionally right. The earlier fake wallet methods were closures and could not detect lost I would approve this. The only minor follow-up I would consider, not a merge blocker, is adding one more regression case showing that an admin-originated privileged-capable method also preserves A second minor point: the explanatory comment in Overall: this is a small, well-scoped fix for a real wallet-connect regression, with a load-bearing regression test. I would merge it. |
Summary
guardVaultAccess(services/vault/guard.ts) wraps everyPRIVILEGED_CAPABLEmethod (getPublicKey,encrypt,decrypt,createHmac,verifyHmac,createSignature,verifySignature,revealCounterpartyKeyLinkage,revealSpecificKeyLinkage,acquireCertificate,proveCertificate,listCertificates) with a privilege check before passing the call through to the real wallet. The pass-through was a bare invocation —value(args, originator)— which dropsthis. The sibling branch two lines above (for methods not inPRIVILEGED_CAPABLE) already does this correctly viavalue.bind(target); this branch never got the same treatment.Real wallet implementations (e.g.
@bsv/wallet-toolbox-mobile'sSimpleWalletManager) readthisinternally:With
thisunbound,this.ensureCanCallresolves toundefined, and calling it throwsundefined is not a function— with no property name in the message, since it's a computed/member call inside a dynamically-evaluated WebView script, which made this very hard to trace from the app side.Impact: this broke every ordinary, non-privileged
getPublicKeycall from the in-tab CWI bridge, not just the privileged ones the guard was meant to block — andgetPublicKeyis the first call a BRC-100 connect makes. That means any website opened in BSV Browser's own in-app browser, using the injectedwindow.CWIsubstrate (and the sameguardVaultAccesscall site backing the desktop-pairingWalletClient), silently fails to connect. Found this by chasing a real "wallet won't connect" report through the whole stack, down to a bareerr.messagewith no context, before finding this line.Why the existing tests didn't catch it
__tests__/vault/guard.test.ts'sfakeWallet()stubs are plain closures that ignorethisentirely — they can't distinguish a bound call from an unbound one. Added a regression test using a real class instance method that readsthis(matching howSimpleWalletManageractually behaves), confirmed it fails against the unfixed code and passes against the fix.Test plan
__tests__/vault/guard.test.tspassthis-loss symptom, passes with the fix)tsc --noEmitclean (two pre-existing, unrelated errors in__tests__/manual/predate this change)window.CWInow succeeds against a real website🤖 Generated with Claude Code