Weakness in Transfer Validation Logic in @solana/pay
Description
Solana Pay is a protocol and set of reference implementations that enable developers to incorporate decentralized payments into their apps and services. When a Solana Pay transaction is located using a reference key, it may be checked to represent a transfer of the desired amount to the recipient, using the supplied validateTransfer function. An edge case regarding this mechanism could cause the validation logic to validate multiple transfers. This issue has been patched as of version 0.2.1. Users of the Solana Pay SDK should upgrade to it. There are no known workarounds for this issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@solana/paynpm | < 0.2.1 | 0.2.1 |
Affected products
1- Range: < 0.2.1
Patches
1ac6ce0d0a811finish transfer validation implementation
1 file changed · +60 −18
core/src/validateTransfer.ts+60 −18 modified@@ -1,15 +1,23 @@ -import { getAssociatedTokenAddress } from '@solana/spl-token'; +import { + decodeInstruction, + getAssociatedTokenAddress, + isTransferCheckedInstruction, + isTransferInstruction, +} from '@solana/spl-token'; import { ConfirmedTransactionMeta, Connection, Finality, LAMPORTS_PER_SOL, Message, + SystemInstruction, + Transaction, TransactionResponse, TransactionSignature, } from '@solana/web3.js'; import BigNumber from 'bignumber.js'; -import { Amount, Memo, Recipient, References, SPLToken } from './types'; +import { MEMO_PROGRAM_ID } from './constants'; +import { Amount, Memo, Recipient, Reference, References, SPLToken } from './types'; /** * Thrown when a transaction doesn't contain a valid Solana Pay transfer. @@ -58,33 +66,49 @@ export async function validateTransfer( if (!meta) throw new ValidateTransferError('missing meta'); if (meta.err) throw meta.err; - const [preAmount, postAmount] = splToken - ? await validateSPLTokenTransfer(message, meta, recipient, splToken) - : await validateSystemTransfer(message, meta, recipient); + if (reference && !Array.isArray(reference)) { + reference = [reference]; + } + const [preAmount, postAmount] = splToken + ? await validateSPLTokenTransfer(message, meta, recipient, splToken, reference) + : await validateSystemTransfer(message, meta, recipient, reference); if (postAmount.minus(preAmount).lt(amount)) throw new ValidateTransferError('amount not transferred'); - if (reference) { - if (!Array.isArray(reference)) { - reference = [reference]; - } - - for (const pubkey of reference) { - if (!message.accountKeys.some((accountKey) => accountKey.equals(pubkey))) - throw new ValidateTransferError('reference not found'); - } + if (memo) { + // Check that the second instruction is a memo instruction with the expected memo. + const transaction = Transaction.populate(message); + const instruction = transaction.instructions[1]; + if (!instruction) throw new ValidateTransferError('missing memo instruction'); + if (!instruction.programId.equals(MEMO_PROGRAM_ID)) throw new ValidateTransferError('invalid memo program'); + if (!instruction.data.equals(Buffer.from(memo, 'utf8'))) throw new ValidateTransferError('invalid memo'); } - // FIXME: add memo check - return response; } async function validateSystemTransfer( message: Message, meta: ConfirmedTransactionMeta, - recipient: Recipient + recipient: Recipient, + references?: Reference[] ): Promise<[BigNumber, BigNumber]> { + if (references) { + // Check that the first instruction is a system transfer instruction. + const transaction = Transaction.populate(message); + const instruction = transaction.instructions[0]; + SystemInstruction.decodeTransfer(instruction); + + // Check that the expected reference keys exactly match the extra keys provided to the instruction. + const [_from, _to, ...extraKeys] = instruction.keys; + const length = extraKeys.length; + if (length !== references.length) throw new ValidateTransferError('invalid references'); + + for (let i = 0; i < length; i++) { + if (!extraKeys[i].pubkey.equals(references[i])) throw new ValidateTransferError(`invalid reference ${i}`); + } + } + const accountIndex = message.accountKeys.findIndex((pubkey) => pubkey.equals(recipient)); if (accountIndex === -1) throw new ValidateTransferError('recipient not found'); @@ -98,8 +122,26 @@ async function validateSPLTokenTransfer( message: Message, meta: ConfirmedTransactionMeta, recipient: Recipient, - splToken: SPLToken + splToken: SPLToken, + references?: Reference[] ): Promise<[BigNumber, BigNumber]> { + if (references) { + // Check that the first instruction is an SPL token transfer instruction. + const transaction = Transaction.populate(message); + const instruction = decodeInstruction(transaction.instructions[0]); + if (!isTransferCheckedInstruction(instruction) && !isTransferInstruction(instruction)) + throw new ValidateTransferError('invalid transfer'); + + // Check that the expected reference keys exactly match the extra keys provided to the instruction. + const extraKeys = instruction.keys.multiSigners; + const length = extraKeys.length; + if (length !== references.length) throw new ValidateTransferError('invalid references'); + + for (let i = 0; i < length; i++) { + if (!extraKeys[i].pubkey.equals(references[i])) throw new ValidateTransferError(`invalid reference ${i}`); + } + } + const recipientATA = await getAssociatedTokenAddress(splToken, recipient); const accountIndex = message.accountKeys.findIndex((pubkey) => pubkey.equals(recipientATA)); if (accountIndex === -1) throw new ValidateTransferError('recipient not found');
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-j47c-j42c-mwqqghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-35917ghsaADVISORY
- github.com/solana-labs/solana-pay/blob/master/SPEC.mdghsax_refsource_MISCWEB
- github.com/solana-labs/solana-pay/blob/master/core/src/validateTransfer.tsghsax_refsource_MISCWEB
- github.com/solana-labs/solana-pay/commit/ac6ce0d0a81137700874a8bf5a7caac3be999fadghsax_refsource_MISCWEB
- github.com/solana-labs/solana-pay/security/advisories/GHSA-j47c-j42c-mwqqghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.