-
Notifications
You must be signed in to change notification settings - Fork 126
Description
Frontend Private Key Exposure Risks Total Wallet Compromise (Critical)
Describe the bug
The Solana RPC handling code in the frontend directly requests the user's raw private key from the connected wallet provider. The key is then used within the browser's JavaScript environment to manually sign and send transactions. This practice fundamentally breaks the security model of modern crypto wallets and exposes users to a catastrophic risk of total fund loss.
Vulnerability
A user's private key should never leave the secure, isolated environment of their wallet (Phantom, Solflare, or a Web3Auth vault). By calling this.provider.request({ method: "solanaPrivateKey" })
, the application brings the most sensitive piece of user data into the insecure context of a web page. If the application is compromised by a Cross Site Scripting (XSS) attack, a malicious script could easily steal this key.
Impact
An attacker who successfully exploits an XSS vulnerability on the site could steal the user's private key. With the private key, the attacker gains complete and permanent control over the user's wallet, allowing them to drain all SOL and any associated tokens (USDC, etc.) and NFTs. This is the most severe impact possible for a user.
Complete Violation of Wallet Security Principles: This design pattern undermines the primary security guarantee of non-custodial wallets, which is that the private key never leaves the user's direct control.
Vulnerable Code
File: frontend/src/solanaRPC.ts
getPrivateKey()
: This function explicitly requests the raw private key from the wallet provider.
sendToken()
: This function calls getPrivateKey()
and then uses the exposed key to create a Keypair
and sign a transaction in the browser (Keypair.fromSecretKey(privateKeyArray
)).
To Reproduce
Open the file frontend/src/solanaRPC.ts
.
Inspect the getPrivateKey
function. Observe that it uses this.provider.request
to request the solanaPrivateKey
, which is the user's raw secret.
Inspect the sendToken
function. Observe that it calls getPrivateKey()
to fetch the key.
Further in sendToken
, observe that the retrieved privateKey
is used to create a senderKeypair (Keypair.fromSecretKey(...)
).
Finally, observe that this senderKeypair
is passed directly into sendAndConfirmTransaction
to sign the transaction, confirming that the signing is happening in the web application's context, not inside the secure wallet.
Present Behavior
The application requests the user's raw private key, brings it into the browser's JavaScript environment, and uses it to sign transactions.
Expected behavior
The application should never request, see, or handle the user's private key. It should construct an unsigned transaction and pass it to the wallet provider's signAndSendTransaction
or signTransaction
method. The wallet would then ask the user for approval and perform the signing securely in its own isolated environment, returning only the resulting transaction signature to the application.
Fix
Refactor the sendToken function and any other transaction sending functions to delegate signing to the wallet provider.
Remove getPrivateKey
: This function and all calls to it must be deleted from the codebase.
Modify the sendToken
function to use the wallet provider's secure signing method. The existing solanaWallet.signAndSendTransaction(transaction)
pattern, which is already used in other functions within the same file (e.g sendTransaction
), is the correct approach. The sendAndConfirmTransaction
call should be replaced with a call to the wallet provider's method.
Correct Implementation
This pattern is already used correctly in the sendTransaction
function in the same file.
// From the sendTransaction function in the same file
const { signature } = await solanaWallet.signAndSendTransaction(
transaction
);