diff --git a/packages/account-sdk/src/sign/base-account/utils/routeThroughGlobalAccount.test.ts b/packages/account-sdk/src/sign/base-account/utils/routeThroughGlobalAccount.test.ts index 82c428b1..5446a645 100644 --- a/packages/account-sdk/src/sign/base-account/utils/routeThroughGlobalAccount.test.ts +++ b/packages/account-sdk/src/sign/base-account/utils/routeThroughGlobalAccount.test.ts @@ -210,6 +210,43 @@ describe('routeThroughGlobalAccount', () => { ); }); + it('should strip request spendPermissions before injecting the sub account spender', async () => { + const attackerAddress = '0x1111111111111111111111111111111111111111'; + const paymasterService = { url: 'https://paymaster.example.com' }; + + // @ts-ignore - testing with mock args + args.request.params[0].capabilities = { + paymasterService, + spendPermissions: { + request: { + spender: attackerAddress, + }, + }, + }; + mockGlobalAccountRequest.mockResolvedValue('0x1234ca11'); + + await routeThroughGlobalAccount(args); + + expect(injectRequestCapabilities).toHaveBeenCalledWith( + expect.objectContaining({ + params: [ + expect.objectContaining({ + capabilities: { + paymasterService, + }, + }), + ], + }), + { + spendPermissions: { + request: { + spender: subAccountAddress, + }, + }, + } + ); + }); + it('should store returned spend permissions in cache', async () => { const mockSpendPermissions = [ { diff --git a/packages/account-sdk/src/sign/base-account/utils/routeThroughGlobalAccount.ts b/packages/account-sdk/src/sign/base-account/utils/routeThroughGlobalAccount.ts index fa1a6743..d1d970db 100644 --- a/packages/account-sdk/src/sign/base-account/utils/routeThroughGlobalAccount.ts +++ b/packages/account-sdk/src/sign/base-account/utils/routeThroughGlobalAccount.ts @@ -107,12 +107,19 @@ export async function routeThroughGlobalAccount({ batchCall, ]; + const { capabilities: originalCapabilities, ...safeSendCallsParams } = originalSendCallsParams; + const safeCapabilities = { ...(originalCapabilities ?? {}) }; + // The dApp controls the original request, so never let it override the SDK-pinned spender. + delete safeCapabilities.spendPermissions; + const hasSafeCapabilities = Object.keys(safeCapabilities).length > 0; + const requestToParent = injectRequestCapabilities( { method: 'wallet_sendCalls', params: [ { - ...originalSendCallsParams, + ...safeSendCallsParams, + ...(hasSafeCapabilities ? { capabilities: safeCapabilities } : {}), calls, from: globalAccountAddress, version: '2.0.0',